minitap-mobile-use 2.7.2__tar.gz → 2.8.0__tar.gz

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.

Files changed (103) hide show
  1. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/PKG-INFO +1 -1
  2. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/main.py +2 -2
  3. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/agent.py +212 -12
  4. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/builders/agent_config_builder.py +27 -0
  5. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/examples/README.md +1 -1
  6. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/examples/platform_manual_task_example.py +2 -2
  7. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/examples/platform_minimal_example.py +2 -3
  8. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/examples/simple_photo_organizer.py +2 -2
  9. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/examples/smart_notification_assistant.py +2 -2
  10. minitap_mobile_use-2.8.0/minitap/mobile_use/sdk/services/cloud_mobile.py +582 -0
  11. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/types/agent.py +3 -0
  12. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/types/exceptions.py +7 -0
  13. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/types/task.py +0 -3
  14. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/pyproject.toml +1 -1
  15. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/LICENSE +0 -0
  16. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/README.md +0 -0
  17. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/__init__.py +0 -0
  18. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/contextor/contextor.py +0 -0
  19. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/cortex/cortex.md +0 -0
  20. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/cortex/cortex.py +0 -0
  21. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/cortex/types.py +0 -0
  22. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/executor/executor.md +0 -0
  23. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/executor/executor.py +0 -0
  24. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/executor/tool_node.py +0 -0
  25. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/executor/utils.py +0 -0
  26. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/hopper/hopper.md +0 -0
  27. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/hopper/hopper.py +0 -0
  28. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/orchestrator/human.md +0 -0
  29. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/orchestrator/orchestrator.md +0 -0
  30. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/orchestrator/orchestrator.py +0 -0
  31. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/orchestrator/types.py +0 -0
  32. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/outputter/human.md +0 -0
  33. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/outputter/outputter.py +0 -0
  34. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/outputter/test_outputter.py +0 -0
  35. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/planner/human.md +0 -0
  36. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/planner/planner.md +0 -0
  37. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/planner/planner.py +0 -0
  38. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/planner/types.py +0 -0
  39. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/planner/utils.py +0 -0
  40. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/screen_analyzer/human.md +0 -0
  41. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/screen_analyzer/screen_analyzer.py +0 -0
  42. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/agents/summarizer/summarizer.py +0 -0
  43. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/clients/device_hardware_client.py +0 -0
  44. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/clients/ios_client.py +0 -0
  45. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/clients/screen_api_client.py +0 -0
  46. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/config.py +0 -0
  47. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/constants.py +0 -0
  48. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/context.py +0 -0
  49. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/controllers/__init__.py +0 -0
  50. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/controllers/mobile_command_controller.py +0 -0
  51. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/controllers/platform_specific_commands_controller.py +0 -0
  52. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/controllers/types.py +0 -0
  53. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/graph/graph.py +0 -0
  54. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/graph/state.py +0 -0
  55. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/__init__.py +0 -0
  56. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/builders/__init__.py +0 -0
  57. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/builders/index.py +0 -0
  58. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/builders/task_request_builder.py +0 -0
  59. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/constants.py +0 -0
  60. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/examples/__init__.py +0 -0
  61. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/services/platform.py +0 -0
  62. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/types/__init__.py +0 -0
  63. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/types/platform.py +0 -0
  64. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/sdk/utils.py +0 -0
  65. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/servers/config.py +0 -0
  66. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/servers/device_hardware_bridge.py +0 -0
  67. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/servers/device_screen_api.py +0 -0
  68. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/servers/start_servers.py +0 -0
  69. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/servers/stop_servers.py +0 -0
  70. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/servers/utils.py +0 -0
  71. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/services/accessibility.py +0 -0
  72. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/services/llm.py +0 -0
  73. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/index.py +0 -0
  74. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/mobile/back.py +0 -0
  75. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/mobile/erase_one_char.py +0 -0
  76. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/mobile/focus_and_clear_text.py +0 -0
  77. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/mobile/focus_and_input_text.py +0 -0
  78. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/mobile/launch_app.py +0 -0
  79. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/mobile/long_press_on.py +0 -0
  80. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/mobile/open_link.py +0 -0
  81. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/mobile/press_key.py +0 -0
  82. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/mobile/stop_app.py +0 -0
  83. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/mobile/swipe.py +0 -0
  84. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/mobile/tap.py +0 -0
  85. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/mobile/wait_for_delay.py +0 -0
  86. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/test_utils.py +0 -0
  87. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/tool_wrapper.py +0 -0
  88. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/types.py +0 -0
  89. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/tools/utils.py +0 -0
  90. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/utils/cli_helpers.py +0 -0
  91. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/utils/cli_selection.py +0 -0
  92. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/utils/conversations.py +0 -0
  93. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/utils/decorators.py +0 -0
  94. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/utils/errors.py +0 -0
  95. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/utils/file.py +0 -0
  96. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/utils/logger.py +0 -0
  97. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/utils/media.py +0 -0
  98. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/utils/recorder.py +0 -0
  99. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/utils/requests_utils.py +0 -0
  100. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/utils/shell_utils.py +0 -0
  101. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/utils/test_ui_hierarchy.py +0 -0
  102. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/utils/time.py +0 -0
  103. {minitap_mobile_use-2.7.2 → minitap_mobile_use-2.8.0}/minitap/mobile_use/utils/ui_hierarchy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: minitap-mobile-use
3
- Version: 2.7.2
3
+ Version: 2.8.0
4
4
  Summary: AI-powered multi-agent system that automates real Android and iOS devices through low-level control using LangGraph.
5
5
  Author: Pierre-Louis Favreau, Jean-Pierre Lo, Nicolas Dehandschoewercker
6
6
  License: MIT License
@@ -43,7 +43,7 @@ async def run_automation(
43
43
  config.with_graph_config_callbacks(graph_config_callbacks)
44
44
 
45
45
  agent = Agent(config=config.build())
46
- agent.init(
46
+ await agent.init(
47
47
  retry_count=int(os.getenv("MOBILE_USE_HEALTH_RETRIES", 5)),
48
48
  retry_wait_seconds=int(os.getenv("MOBILE_USE_HEALTH_DELAY", 2)),
49
49
  )
@@ -63,7 +63,7 @@ async def run_automation(
63
63
 
64
64
  await agent.run_task(request=task.build())
65
65
 
66
- agent.clean()
66
+ await agent.clean()
67
67
 
68
68
 
69
69
  @app.command()
@@ -13,6 +13,7 @@ from typing import Any, TypeVar, overload
13
13
  from adbutils import AdbClient
14
14
  from dotenv import load_dotenv
15
15
  from langchain_core.messages import AIMessage
16
+ from PIL import Image
16
17
  from pydantic import BaseModel
17
18
 
18
19
  from minitap.mobile_use.agents.outputter.outputter import outputter
@@ -37,12 +38,14 @@ from minitap.mobile_use.graph.state import State
37
38
  from minitap.mobile_use.sdk.builders.agent_config_builder import get_default_agent_config
38
39
  from minitap.mobile_use.sdk.builders.task_request_builder import TaskRequestBuilder
39
40
  from minitap.mobile_use.sdk.constants import DEFAULT_HW_BRIDGE_BASE_URL, DEFAULT_SCREEN_API_BASE_URL
41
+ from minitap.mobile_use.sdk.services.cloud_mobile import CloudMobileService
40
42
  from minitap.mobile_use.sdk.services.platform import PlatformService
41
43
  from minitap.mobile_use.sdk.types.agent import AgentConfig
42
44
  from minitap.mobile_use.sdk.types.exceptions import (
43
45
  AgentNotInitializedError,
44
46
  AgentProfileNotFoundError,
45
47
  AgentTaskRequestError,
48
+ CloudMobileServiceUninitializedError,
46
49
  DeviceNotFoundError,
47
50
  ExecutableNotFoundError,
48
51
  PlatformServiceUninitializedError,
@@ -92,6 +95,7 @@ class Agent:
92
95
  _adb_client: AdbClient | None
93
96
  _current_task: asyncio.Task | None = None
94
97
  _task_lock: asyncio.Lock
98
+ _cloud_mobile_id: str | None = None
95
99
 
96
100
  def __init__(self, *, config: AgentConfig | None = None):
97
101
  self._config = config or get_default_agent_config()
@@ -105,19 +109,38 @@ class Agent:
105
109
  self._config.servers.screen_api_base_url == DEFAULT_SCREEN_API_BASE_URL
106
110
  )
107
111
  self._task_lock = asyncio.Lock()
112
+
108
113
  # Initialize platform service if API key is available in environment
109
- # Note: Can also be initialized later with API key from request
114
+ # Note: Can also be initialized later with API key at agent .init()
110
115
  if settings.MINITAP_API_KEY:
111
116
  self._platform_service = PlatformService()
117
+ self._cloud_mobile_service = CloudMobileService()
112
118
  else:
113
119
  self._platform_service = None
120
+ self._cloud_mobile_service = None
114
121
 
115
- def init(
122
+ async def init(
116
123
  self,
124
+ api_key: str | None = None,
117
125
  server_restart_attempts: int = 3,
118
126
  retry_count: int = 5,
119
127
  retry_wait_seconds: int = 5,
120
128
  ):
129
+ if api_key:
130
+ self._platform_service = PlatformService(api_key=api_key)
131
+ self._cloud_mobile_service = CloudMobileService(api_key=api_key)
132
+
133
+ # Skip initialization for cloud devices - no local setup required
134
+ if self._config.cloud_mobile_id_or_ref:
135
+ if not self._cloud_mobile_service:
136
+ raise CloudMobileServiceUninitializedError()
137
+ self._cloud_mobile_id = await self._cloud_mobile_service.resolve_cloud_mobile_id(
138
+ cloud_mobile_id_or_ref=self._config.cloud_mobile_id_or_ref,
139
+ )
140
+ logger.info("Cloud device configured - skipping local initialization")
141
+ self._initialized = True
142
+ return True
143
+
121
144
  if not which("adb") and not which("xcrun"):
122
145
  raise ExecutableNotFoundError("cli_tools")
123
146
  if self._is_default_hw_bridge and not which("maestro"):
@@ -233,25 +256,30 @@ class Agent:
233
256
  name: str | None = None,
234
257
  request: TaskRequest[TOutput] | PlatformTaskRequest[TOutput] | None = None,
235
258
  ) -> str | dict | TOutput | None:
259
+ # Check if cloud mobile is configured
260
+ if self._config.cloud_mobile_id_or_ref:
261
+ if request is None or not isinstance(request, PlatformTaskRequest):
262
+ raise AgentTaskRequestError(
263
+ "When using a cloud mobile, only PlatformTaskRequest is supported. "
264
+ "Use AgentConfigBuilder.for_cloud_mobile() only with PlatformTaskRequest."
265
+ )
266
+ # Use cloud mobile execution path
267
+ return await self._run_cloud_mobile_task(request=request)
268
+
269
+ # Normal local execution path
236
270
  if request is not None:
237
271
  task_info = None
238
- platform_service = None
239
272
  if isinstance(request, PlatformTaskRequest):
240
- # Initialize platform service with API key from request if provided
241
- if request.api_key:
242
- platform_service = PlatformService(api_key=request.api_key)
243
- elif self._platform_service:
244
- platform_service = self._platform_service
245
- else:
273
+ if not self._platform_service:
246
274
  raise PlatformServiceUninitializedError()
247
- task_info = await platform_service.create_task_run(request=request)
275
+ task_info = await self._platform_service.create_task_run(request=request)
248
276
  if isinstance(request, CloudDevicePlatformTaskRequest):
249
277
  request.task_run_id = task_info.task_run.id
250
278
  request.task_run_id_available_event.set()
251
279
  self._config.agent_profiles[task_info.llm_profile.name] = task_info.llm_profile
252
280
  request = task_info.task_request
253
281
  return await self._run_task(
254
- request=request, task_info=task_info, platform_service=platform_service
282
+ request=request, task_info=task_info, platform_service=self._platform_service
255
283
  )
256
284
  if goal is None:
257
285
  raise AgentTaskRequestError("Goal is required")
@@ -267,6 +295,96 @@ class Agent:
267
295
  task_request.with_name(name=name)
268
296
  return await self._run_task(task_request.build())
269
297
 
298
+ async def _run_cloud_mobile_task(
299
+ self,
300
+ request: PlatformTaskRequest[TOutput],
301
+ ) -> str | dict | TOutput | None:
302
+ """
303
+ Execute a task on a cloud mobile.
304
+
305
+ This method triggers the task execution on the Platform and polls
306
+ for completion without running any agentic logic locally.
307
+ """
308
+ if not self._cloud_mobile_id:
309
+ raise AgentTaskRequestError("Cloud mobile ID is not configured")
310
+
311
+ if not self._cloud_mobile_service:
312
+ raise CloudMobileServiceUninitializedError()
313
+
314
+ # Start cloud mobile if not already started
315
+ logger.info(f"Starting cloud mobile '{self._cloud_mobile_id}'...")
316
+ await self._cloud_mobile_service.start_and_wait_for_ready(
317
+ cloud_mobile_id=self._cloud_mobile_id,
318
+ )
319
+ logger.info(
320
+ f"Starting cloud mobile task execution '{self._cloud_mobile_id}'",
321
+ )
322
+
323
+ def log_callback(message: str):
324
+ """Callback for logging timeline updates."""
325
+ logger.info(message)
326
+
327
+ def status_callback(
328
+ status: TaskRunStatus,
329
+ status_message: str | None,
330
+ ):
331
+ """Callback for status updates."""
332
+ logger.info(f"Task status update: [{status}] {status_message}")
333
+
334
+ async def _execute_cloud(cloud_mobile_service: CloudMobileService, cloud_mobile_id: str):
335
+ try:
336
+ # Execute task on cloud mobile and wait for completion
337
+ final_status, error, output = await cloud_mobile_service.run_task_on_cloud_mobile(
338
+ cloud_mobile_id=cloud_mobile_id,
339
+ request=request,
340
+ on_status_update=status_callback,
341
+ on_log=log_callback,
342
+ )
343
+ if final_status == "completed":
344
+ logger.success("Cloud mobile task completed successfully")
345
+ return output
346
+ if final_status == "failed":
347
+ logger.error(f"Cloud mobile task failed: {error}")
348
+ raise AgentTaskRequestError(
349
+ f"Task execution failed on cloud mobile: {error}",
350
+ )
351
+ if final_status == "cancelled":
352
+ logger.warning("Cloud mobile task was cancelled")
353
+ raise AgentTaskRequestError("Task execution was cancelled")
354
+ logger.error(f"Unknown cloud mobile task status: {final_status}")
355
+ raise AgentTaskRequestError(f"Unknown task status: {final_status}")
356
+ except asyncio.CancelledError:
357
+ # Propagate cancellation to parent coroutine.
358
+ logger.info("Task cancelled during execution, re-raising CancelledError")
359
+ raise
360
+ except AgentTaskRequestError:
361
+ # Re-raise known exceptions
362
+ raise
363
+ except Exception as e:
364
+ logger.error(f"Unexpected error during cloud mobile task execution: {e}")
365
+ raise AgentTaskRequestError(f"Unexpected error: {e}") from e
366
+
367
+ async with self._task_lock:
368
+ if self._current_task and not self._current_task.done():
369
+ logger.warning(
370
+ "Another cloud task is running; cancelling it before starting new one.",
371
+ )
372
+ self.stop_current_task()
373
+ try:
374
+ await self._current_task
375
+ except asyncio.CancelledError:
376
+ pass
377
+ try:
378
+ self._current_task = asyncio.create_task(
379
+ _execute_cloud(
380
+ cloud_mobile_service=self._cloud_mobile_service,
381
+ cloud_mobile_id=self._cloud_mobile_id,
382
+ ),
383
+ )
384
+ return await self._current_task
385
+ finally:
386
+ self._current_task = None
387
+
270
388
  async def _run_task(
271
389
  self,
272
390
  request: TaskRequest[TOutput],
@@ -456,6 +574,9 @@ class Agent:
456
574
  Uses the configured Screen API base URL instead of hardcoding localhost.
457
575
  """
458
576
  try:
577
+ # In cloud mode, local streaming health is irrelevant.
578
+ if self._config.cloud_mobile_id_or_ref:
579
+ return True
459
580
  response = self._screen_api_client.get_with_retry("/streaming-status", timeout=2)
460
581
  if response.status_code == 200:
461
582
  data = response.json()
@@ -465,7 +586,86 @@ class Agent:
465
586
  except Exception:
466
587
  return False
467
588
 
468
- def clean(self, force: bool = False):
589
+ async def get_screenshot(self) -> Image.Image:
590
+ """
591
+ Capture a screenshot from the mobile device.
592
+
593
+ For cloud mobiles, this method calls the mobile-manager endpoint.
594
+ For local mobiles, it uses ADB (Android) or xcrun (iOS) directly.
595
+
596
+ Returns:
597
+ Screenshot as PIL Image
598
+
599
+ Raises:
600
+ AgentNotInitializedError: If the agent is not initialized
601
+ PlatformServiceUninitializedError: If cloud mobile service is not available
602
+ Exception: If screenshot capture fails
603
+ """
604
+ # Check if cloud mobile is configured
605
+ if self._cloud_mobile_id:
606
+ if not self._cloud_mobile_service:
607
+ raise CloudMobileServiceUninitializedError()
608
+ screenshot = await self._cloud_mobile_service.get_screenshot(
609
+ cloud_mobile_id=self._cloud_mobile_id,
610
+ )
611
+ return screenshot
612
+
613
+ # Local device - use ADB or xcrun directly
614
+ if not self._initialized:
615
+ raise AgentNotInitializedError()
616
+
617
+ if self._device_context.mobile_platform == DevicePlatform.ANDROID:
618
+ # Use ADB to capture screenshot
619
+ logger.info("Capturing screenshot from local Android device")
620
+ if not self._adb_client:
621
+ raise Exception("ADB client not initialized")
622
+
623
+ device = self._adb_client.device(serial=self._device_context.device_id)
624
+ screenshot = await asyncio.to_thread(device.screenshot)
625
+ logger.info("Screenshot captured from local Android device")
626
+ return screenshot
627
+
628
+ elif self._device_context.mobile_platform == DevicePlatform.IOS:
629
+ # Use xcrun to capture screenshot
630
+ import functools
631
+ import subprocess
632
+ from io import BytesIO
633
+
634
+ logger.info("Capturing screenshot from local iOS device")
635
+ try:
636
+ # xcrun simctl io <device> screenshot --type=png -
637
+ result = await asyncio.to_thread(
638
+ functools.partial(
639
+ subprocess.run,
640
+ [
641
+ "xcrun",
642
+ "simctl",
643
+ "io",
644
+ self._device_context.device_id,
645
+ "screenshot",
646
+ "--type=png",
647
+ "-",
648
+ ],
649
+ capture_output=True,
650
+ check=True,
651
+ )
652
+ )
653
+ # Convert bytes to PIL Image
654
+ screenshot = Image.open(BytesIO(result.stdout))
655
+ logger.info("Screenshot captured from local iOS device")
656
+ return screenshot
657
+ except subprocess.CalledProcessError as e:
658
+ logger.error(f"Failed to capture screenshot: {e}")
659
+ raise Exception(f"Failed to capture screenshot from iOS device: {e}")
660
+
661
+ else:
662
+ raise Exception(f"Unsupported platform: {self._device_context.mobile_platform}")
663
+
664
+ async def clean(self, force: bool = False):
665
+ if self._cloud_mobile_id:
666
+ self._initialized = False
667
+ logger.info("✅ Cloud-mode agent stopped.")
668
+ return
469
669
  if not self._initialized and not force:
470
670
  return
471
671
  screen_api_ok, hw_bridge_ok = stop_servers(
@@ -45,6 +45,7 @@ class AgentConfigBuilder:
45
45
  self._device_platform: DevicePlatform | None = None
46
46
  self._servers: ServerConfig = get_default_servers()
47
47
  self._graph_config_callbacks: Callbacks = None
48
+ self._cloud_mobile_id_or_ref: str | None = None
48
49
 
49
50
  def add_profile(self, profile: AgentProfile, validate: bool = True) -> "AgentConfigBuilder":
50
51
  """
@@ -95,10 +96,35 @@ class AgentConfigBuilder:
95
96
  platform: The device platform (ANDROID or IOS)
96
97
  device_id: The unique identifier for the device
97
98
  """
99
+ if self._cloud_mobile_id_or_ref is not None:
100
+ raise ValueError(
101
+ "Device ID cannot be set when a cloud mobile is already configured.\n"
102
+ "> for_device() and for_cloud_mobile() are mutually exclusive"
103
+ )
98
104
  self._device_id = device_id
99
105
  self._device_platform = platform
100
106
  return self
101
107
 
108
+ def for_cloud_mobile(self, cloud_mobile_id_or_ref: str) -> "AgentConfigBuilder":
109
+ """
110
+ Configure the mobile-use agent to use a cloud mobile.
111
+
112
+ When using a cloud mobile, tasks are executed remotely via the Platform API,
113
+ and only PlatformTaskRequest can be used.
114
+
115
+ Args:
116
+ cloud_mobile_id_or_ref: The unique identifier or reference name for the cloud mobile.
117
+ Can be either a UUID (e.g., '550e8400-e29b-41d4-a716-446655440000')
118
+ or a reference name (e.g., 'my-test-device')
119
+ """
120
+ if self._device_id is not None:
121
+ raise ValueError(
122
+ "Cloud mobile device ID cannot be set when a device is already configured.\n"
123
+ "> for_device() and for_cloud_mobile() are mutually exclusive"
124
+ )
125
+ self._cloud_mobile_id_or_ref = cloud_mobile_id_or_ref
126
+ return self
127
+
102
128
  def with_default_task_config(self, config: TaskRequestCommon) -> "AgentConfigBuilder":
103
129
  """
104
130
  Set the default task configuration.
@@ -217,6 +243,7 @@ class AgentConfigBuilder:
217
243
  device_platform=self._device_platform,
218
244
  servers=self._servers,
219
245
  graph_config_callbacks=self._graph_config_callbacks,
246
+ cloud_mobile_id_or_ref=self._cloud_mobile_id_or_ref,
220
247
  )
221
248
 
222
249
 
@@ -15,7 +15,7 @@ These examples demonstrate two different ways to use the SDK, each applying an a
15
15
  This script shows the simplest way to run minitap :
16
16
 
17
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=...).
18
+ - Initialize the agent with your API key: .init(api_key=...).
19
19
  - Ask the agent to run one of the tasks you’ve set up in the Minitap platform
20
20
  (e.g., "like-instagram-post").
21
21
  - The task’s goal and settings live in the Minitap platform, you don’t need
@@ -34,7 +34,7 @@ async def main() -> None:
34
34
  Set MINITAP_API_KEY and MINITAP_BASE_URL environment variables.
35
35
  """
36
36
  agent = Agent()
37
- agent.init()
37
+ await agent.init()
38
38
 
39
39
  # Example 1: Simple manual task
40
40
  result = await agent.run_task(
@@ -58,7 +58,7 @@ async def main() -> None:
58
58
  )
59
59
  print("Result 2:", result)
60
60
 
61
- agent.clean()
61
+ await agent.clean()
62
62
 
63
63
 
64
64
  if __name__ == "__main__":
@@ -31,16 +31,15 @@ async def main() -> None:
31
31
  Set MINITAP_API_KEY and MINITAP_BASE_URL environment variables.
32
32
  """
33
33
  agent = Agent()
34
- agent.init()
34
+ await agent.init(api_key="<api-key>") # or set MINITAP_API_KEY env variable
35
35
  result = await agent.run_task(
36
36
  request=PlatformTaskRequest(
37
37
  task="your-task-name",
38
38
  profile="your-profile-name",
39
- api_key="<api-key>", # or set MINITAP_API_KEY env variable
40
39
  )
41
40
  )
42
41
  print(result)
43
- agent.clean()
42
+ await agent.clean()
44
43
 
45
44
 
46
45
  if __name__ == "__main__":
@@ -33,7 +33,7 @@ async def main() -> None:
33
33
 
34
34
  try:
35
35
  # Initialize agent (finds a device, starts required servers)
36
- agent.init()
36
+ await agent.init()
37
37
 
38
38
  # Calculate yesterday's date for the example
39
39
  yesterday = date.today() - timedelta(days=1)
@@ -69,7 +69,7 @@ async def main() -> None:
69
69
  print(f"Error: {e}")
70
70
  finally:
71
71
  # Always clean up resources
72
- agent.clean()
72
+ await agent.clean()
73
73
 
74
74
 
75
75
  if __name__ == "__main__":
@@ -167,7 +167,7 @@ async def main():
167
167
 
168
168
  try:
169
169
  # Initialize agent (finds a device, starts required servers)
170
- agent.init()
170
+ await agent.init()
171
171
 
172
172
  print("Checking for notifications...")
173
173
 
@@ -217,7 +217,7 @@ async def main():
217
217
 
218
218
  finally:
219
219
  # Clean up
220
- agent.clean()
220
+ await agent.clean()
221
221
  print(f"\nTraces saved to: {traces_dir}")
222
222
 
223
223