minitap-mobile-use 2.3.0__py3-none-any.whl → 2.5.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.

Files changed (56) hide show
  1. minitap/mobile_use/agents/contextor/contextor.py +2 -2
  2. minitap/mobile_use/agents/cortex/cortex.md +49 -8
  3. minitap/mobile_use/agents/cortex/cortex.py +8 -4
  4. minitap/mobile_use/agents/executor/executor.md +14 -11
  5. minitap/mobile_use/agents/executor/executor.py +6 -5
  6. minitap/mobile_use/agents/hopper/hopper.py +6 -3
  7. minitap/mobile_use/agents/orchestrator/orchestrator.py +26 -11
  8. minitap/mobile_use/agents/outputter/outputter.py +6 -3
  9. minitap/mobile_use/agents/planner/planner.md +20 -22
  10. minitap/mobile_use/agents/planner/planner.py +10 -7
  11. minitap/mobile_use/agents/planner/types.py +4 -2
  12. minitap/mobile_use/agents/planner/utils.py +14 -0
  13. minitap/mobile_use/agents/summarizer/summarizer.py +2 -2
  14. minitap/mobile_use/config.py +6 -1
  15. minitap/mobile_use/context.py +13 -3
  16. minitap/mobile_use/controllers/mobile_command_controller.py +1 -14
  17. minitap/mobile_use/graph/state.py +7 -3
  18. minitap/mobile_use/sdk/agent.py +188 -23
  19. minitap/mobile_use/sdk/examples/README.md +19 -1
  20. minitap/mobile_use/sdk/examples/platform_manual_task_example.py +65 -0
  21. minitap/mobile_use/sdk/examples/platform_minimal_example.py +46 -0
  22. minitap/mobile_use/sdk/services/platform.py +307 -0
  23. minitap/mobile_use/sdk/types/__init__.py +16 -14
  24. minitap/mobile_use/sdk/types/exceptions.py +27 -0
  25. minitap/mobile_use/sdk/types/platform.py +127 -0
  26. minitap/mobile_use/sdk/types/task.py +78 -17
  27. minitap/mobile_use/servers/device_hardware_bridge.py +1 -1
  28. minitap/mobile_use/servers/stop_servers.py +11 -12
  29. minitap/mobile_use/services/llm.py +89 -5
  30. minitap/mobile_use/tools/index.py +0 -6
  31. minitap/mobile_use/tools/mobile/back.py +3 -3
  32. minitap/mobile_use/tools/mobile/clear_text.py +24 -43
  33. minitap/mobile_use/tools/mobile/erase_one_char.py +5 -4
  34. minitap/mobile_use/tools/mobile/glimpse_screen.py +11 -7
  35. minitap/mobile_use/tools/mobile/input_text.py +21 -51
  36. minitap/mobile_use/tools/mobile/launch_app.py +54 -22
  37. minitap/mobile_use/tools/mobile/long_press_on.py +15 -8
  38. minitap/mobile_use/tools/mobile/open_link.py +15 -8
  39. minitap/mobile_use/tools/mobile/press_key.py +15 -8
  40. minitap/mobile_use/tools/mobile/stop_app.py +14 -8
  41. minitap/mobile_use/tools/mobile/swipe.py +11 -5
  42. minitap/mobile_use/tools/mobile/tap.py +103 -21
  43. minitap/mobile_use/tools/mobile/wait_for_animation_to_end.py +3 -3
  44. minitap/mobile_use/tools/test_utils.py +104 -78
  45. minitap/mobile_use/tools/types.py +35 -0
  46. minitap/mobile_use/tools/utils.py +51 -48
  47. minitap/mobile_use/utils/recorder.py +1 -1
  48. minitap/mobile_use/utils/ui_hierarchy.py +9 -2
  49. {minitap_mobile_use-2.3.0.dist-info → minitap_mobile_use-2.5.0.dist-info}/METADATA +3 -1
  50. minitap_mobile_use-2.5.0.dist-info/RECORD +100 -0
  51. minitap/mobile_use/tools/mobile/copy_text_from.py +0 -75
  52. minitap/mobile_use/tools/mobile/find_packages.py +0 -69
  53. minitap/mobile_use/tools/mobile/paste_text.py +0 -88
  54. minitap_mobile_use-2.3.0.dist-info/RECORD +0 -98
  55. {minitap_mobile_use-2.3.0.dist-info → minitap_mobile_use-2.5.0.dist-info}/WHEEL +0 -0
  56. {minitap_mobile_use-2.3.0.dist-info → minitap_mobile_use-2.5.0.dist-info}/entry_points.txt +0 -0
@@ -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
- trace_id: str
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 sanitize_update(
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)))
@@ -3,24 +3,28 @@ import sys
3
3
  import tempfile
4
4
  import time
5
5
  import uuid
6
- from datetime import datetime
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
- DEFAULT_HW_BRIDGE_BASE_URL,
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.task import AgentProfile, Task, TaskRequest, TaskStatus
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
- return await self._run_task(request)
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(self, request: TaskRequest[TOutput]) -> str | dict | TOutput | None:
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=str(uuid.uuid4()),
297
+ id=task_id,
238
298
  device=self._device_context,
239
- status=TaskStatus.PENDING,
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 = TaskStatus.RUNNING
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(content=output, state=last_state_snapshot, error=err, cancelled=True)
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(content=output, state=last_state_snapshot, error=err)
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, trace_id=task_name
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 == TaskStatus.COMPLETED else "_FAIL"
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.trace_id}{status}_{ts}"
450
+ new_name = f"{exec_setup_ctx.trace_name}{status}_{ts}"
370
451
 
371
- temp_trace_path = (self._tmp_traces_dir / exec_setup_ctx.trace_id).resolve()
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**: Use the direct approach (like in `simple_photo_organizer.py`) for simple tasks and the builder approach (like in `smart_notification_assistant.py`) when you need more customization.
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,65 @@
1
+ """
2
+ Platform Usage - Manual Task Creation Example
3
+
4
+ This example demonstrates how to use the mobile-use SDK with manual task creation:
5
+ - Agent with minitap_api_key
6
+ - PlatformTaskRequest with ManualTaskConfig instead of task_id
7
+ - Task configuration provided directly in code (goal, output_description)
8
+ - No need to pre-create task in platform UI
9
+
10
+ Platform Model:
11
+ - API key provides authentication and agent configuration
12
+ - ManualTaskConfig creates task on-the-fly with:
13
+ - max_steps: 400 (fixed)
14
+ - enable_remote_tracing: True (fixed)
15
+ - profile: "default" (fixed)
16
+ - goal: provided by you
17
+ - output_description: provided by you (optional)
18
+
19
+ Run:
20
+ - python src/mobile_use/sdk/examples/platform_manual_task_example.py
21
+ """
22
+
23
+ import asyncio
24
+
25
+ from minitap.mobile_use.sdk import Agent
26
+ from minitap.mobile_use.sdk.types import ManualTaskConfig, PlatformTaskRequest
27
+
28
+
29
+ async def main() -> None:
30
+ """
31
+ Main execution function demonstrating manual task creation pattern.
32
+
33
+ Visit https://platform.minitap.ai to get your API key.
34
+ Set MINITAP_API_KEY and MINITAP_API_BASE_URL environment variables.
35
+ """
36
+ agent = Agent()
37
+ agent.init()
38
+
39
+ # Example 1: Simple manual task
40
+ result = await agent.run_task(
41
+ request=PlatformTaskRequest(
42
+ task=ManualTaskConfig(
43
+ goal="Open the settings app and tell me the battery level",
44
+ ),
45
+ profile="default", # Optional, defaults to "default"
46
+ )
47
+ )
48
+ print("Result 1:", result)
49
+
50
+ # Example 2: Manual task with output description
51
+ result = await agent.run_task(
52
+ request=PlatformTaskRequest(
53
+ task=ManualTaskConfig(
54
+ goal="Find the first 3 unread emails in Gmail",
55
+ output_description="A JSON array with sender and subject for each email",
56
+ ),
57
+ )
58
+ )
59
+ print("Result 2:", result)
60
+
61
+ agent.clean()
62
+
63
+
64
+ if __name__ == "__main__":
65
+ asyncio.run(main())
@@ -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())