minitap-mobile-use 3.3.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.
Files changed (115) hide show
  1. minitap/mobile_use/__init__.py +0 -0
  2. minitap/mobile_use/agents/contextor/contextor.md +55 -0
  3. minitap/mobile_use/agents/contextor/contextor.py +175 -0
  4. minitap/mobile_use/agents/contextor/types.py +36 -0
  5. minitap/mobile_use/agents/cortex/cortex.md +135 -0
  6. minitap/mobile_use/agents/cortex/cortex.py +152 -0
  7. minitap/mobile_use/agents/cortex/types.py +15 -0
  8. minitap/mobile_use/agents/executor/executor.md +42 -0
  9. minitap/mobile_use/agents/executor/executor.py +87 -0
  10. minitap/mobile_use/agents/executor/tool_node.py +152 -0
  11. minitap/mobile_use/agents/hopper/hopper.md +15 -0
  12. minitap/mobile_use/agents/hopper/hopper.py +44 -0
  13. minitap/mobile_use/agents/orchestrator/human.md +12 -0
  14. minitap/mobile_use/agents/orchestrator/orchestrator.md +21 -0
  15. minitap/mobile_use/agents/orchestrator/orchestrator.py +134 -0
  16. minitap/mobile_use/agents/orchestrator/types.py +11 -0
  17. minitap/mobile_use/agents/outputter/human.md +25 -0
  18. minitap/mobile_use/agents/outputter/outputter.py +85 -0
  19. minitap/mobile_use/agents/outputter/test_outputter.py +167 -0
  20. minitap/mobile_use/agents/planner/human.md +14 -0
  21. minitap/mobile_use/agents/planner/planner.md +126 -0
  22. minitap/mobile_use/agents/planner/planner.py +101 -0
  23. minitap/mobile_use/agents/planner/types.py +51 -0
  24. minitap/mobile_use/agents/planner/utils.py +70 -0
  25. minitap/mobile_use/agents/summarizer/summarizer.py +35 -0
  26. minitap/mobile_use/agents/video_analyzer/__init__.py +5 -0
  27. minitap/mobile_use/agents/video_analyzer/human.md +5 -0
  28. minitap/mobile_use/agents/video_analyzer/video_analyzer.md +37 -0
  29. minitap/mobile_use/agents/video_analyzer/video_analyzer.py +111 -0
  30. minitap/mobile_use/clients/browserstack_client.py +477 -0
  31. minitap/mobile_use/clients/idb_client.py +429 -0
  32. minitap/mobile_use/clients/ios_client.py +332 -0
  33. minitap/mobile_use/clients/ios_client_config.py +141 -0
  34. minitap/mobile_use/clients/ui_automator_client.py +330 -0
  35. minitap/mobile_use/clients/wda_client.py +526 -0
  36. minitap/mobile_use/clients/wda_lifecycle.py +367 -0
  37. minitap/mobile_use/config.py +413 -0
  38. minitap/mobile_use/constants.py +3 -0
  39. minitap/mobile_use/context.py +106 -0
  40. minitap/mobile_use/controllers/__init__.py +0 -0
  41. minitap/mobile_use/controllers/android_controller.py +524 -0
  42. minitap/mobile_use/controllers/controller_factory.py +46 -0
  43. minitap/mobile_use/controllers/device_controller.py +182 -0
  44. minitap/mobile_use/controllers/ios_controller.py +436 -0
  45. minitap/mobile_use/controllers/platform_specific_commands_controller.py +199 -0
  46. minitap/mobile_use/controllers/types.py +106 -0
  47. minitap/mobile_use/controllers/unified_controller.py +193 -0
  48. minitap/mobile_use/graph/graph.py +160 -0
  49. minitap/mobile_use/graph/state.py +115 -0
  50. minitap/mobile_use/main.py +309 -0
  51. minitap/mobile_use/sdk/__init__.py +12 -0
  52. minitap/mobile_use/sdk/agent.py +1294 -0
  53. minitap/mobile_use/sdk/builders/__init__.py +10 -0
  54. minitap/mobile_use/sdk/builders/agent_config_builder.py +307 -0
  55. minitap/mobile_use/sdk/builders/index.py +15 -0
  56. minitap/mobile_use/sdk/builders/task_request_builder.py +236 -0
  57. minitap/mobile_use/sdk/constants.py +1 -0
  58. minitap/mobile_use/sdk/examples/README.md +83 -0
  59. minitap/mobile_use/sdk/examples/__init__.py +1 -0
  60. minitap/mobile_use/sdk/examples/app_lock_messaging.py +54 -0
  61. minitap/mobile_use/sdk/examples/platform_manual_task_example.py +67 -0
  62. minitap/mobile_use/sdk/examples/platform_minimal_example.py +48 -0
  63. minitap/mobile_use/sdk/examples/simple_photo_organizer.py +76 -0
  64. minitap/mobile_use/sdk/examples/smart_notification_assistant.py +225 -0
  65. minitap/mobile_use/sdk/examples/video_transcription_example.py +117 -0
  66. minitap/mobile_use/sdk/services/cloud_mobile.py +656 -0
  67. minitap/mobile_use/sdk/services/platform.py +434 -0
  68. minitap/mobile_use/sdk/types/__init__.py +51 -0
  69. minitap/mobile_use/sdk/types/agent.py +84 -0
  70. minitap/mobile_use/sdk/types/exceptions.py +138 -0
  71. minitap/mobile_use/sdk/types/platform.py +183 -0
  72. minitap/mobile_use/sdk/types/task.py +269 -0
  73. minitap/mobile_use/sdk/utils.py +29 -0
  74. minitap/mobile_use/services/accessibility.py +100 -0
  75. minitap/mobile_use/services/llm.py +247 -0
  76. minitap/mobile_use/services/telemetry.py +421 -0
  77. minitap/mobile_use/tools/index.py +67 -0
  78. minitap/mobile_use/tools/mobile/back.py +52 -0
  79. minitap/mobile_use/tools/mobile/erase_one_char.py +56 -0
  80. minitap/mobile_use/tools/mobile/focus_and_clear_text.py +317 -0
  81. minitap/mobile_use/tools/mobile/focus_and_input_text.py +153 -0
  82. minitap/mobile_use/tools/mobile/launch_app.py +86 -0
  83. minitap/mobile_use/tools/mobile/long_press_on.py +169 -0
  84. minitap/mobile_use/tools/mobile/open_link.py +62 -0
  85. minitap/mobile_use/tools/mobile/press_key.py +83 -0
  86. minitap/mobile_use/tools/mobile/stop_app.py +62 -0
  87. minitap/mobile_use/tools/mobile/swipe.py +156 -0
  88. minitap/mobile_use/tools/mobile/tap.py +154 -0
  89. minitap/mobile_use/tools/mobile/video_recording.py +177 -0
  90. minitap/mobile_use/tools/mobile/wait_for_delay.py +81 -0
  91. minitap/mobile_use/tools/scratchpad.py +147 -0
  92. minitap/mobile_use/tools/test_utils.py +413 -0
  93. minitap/mobile_use/tools/tool_wrapper.py +16 -0
  94. minitap/mobile_use/tools/types.py +35 -0
  95. minitap/mobile_use/tools/utils.py +336 -0
  96. minitap/mobile_use/utils/app_launch_utils.py +173 -0
  97. minitap/mobile_use/utils/cli_helpers.py +37 -0
  98. minitap/mobile_use/utils/cli_selection.py +143 -0
  99. minitap/mobile_use/utils/conversations.py +31 -0
  100. minitap/mobile_use/utils/decorators.py +124 -0
  101. minitap/mobile_use/utils/errors.py +6 -0
  102. minitap/mobile_use/utils/file.py +13 -0
  103. minitap/mobile_use/utils/logger.py +183 -0
  104. minitap/mobile_use/utils/media.py +186 -0
  105. minitap/mobile_use/utils/recorder.py +52 -0
  106. minitap/mobile_use/utils/requests_utils.py +37 -0
  107. minitap/mobile_use/utils/shell_utils.py +20 -0
  108. minitap/mobile_use/utils/test_ui_hierarchy.py +178 -0
  109. minitap/mobile_use/utils/time.py +6 -0
  110. minitap/mobile_use/utils/ui_hierarchy.py +132 -0
  111. minitap/mobile_use/utils/video.py +281 -0
  112. minitap_mobile_use-3.3.0.dist-info/METADATA +329 -0
  113. minitap_mobile_use-3.3.0.dist-info/RECORD +115 -0
  114. minitap_mobile_use-3.3.0.dist-info/WHEEL +4 -0
  115. minitap_mobile_use-3.3.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,177 @@
1
+ """
2
+ Video recording tools for mobile devices.
3
+
4
+ Provides start/stop video recording tools that delegate to platform-specific
5
+ controllers (AndroidDeviceController, iOSDeviceController).
6
+ """
7
+
8
+ import shutil
9
+ from typing import Annotated
10
+
11
+ from langchain_core.messages import ToolMessage
12
+ from langchain_core.tools import tool
13
+ from langchain_core.tools.base import BaseTool, InjectedToolCallId
14
+ from langgraph.prebuilt import InjectedState
15
+ from langgraph.types import Command
16
+
17
+ from minitap.mobile_use.constants import EXECUTOR_MESSAGES_KEY
18
+ from minitap.mobile_use.context import DevicePlatform, MobileUseContext
19
+ from minitap.mobile_use.controllers.controller_factory import get_controller
20
+ from minitap.mobile_use.graph.state import State
21
+ from minitap.mobile_use.tools.tool_wrapper import ToolWrapper
22
+ from minitap.mobile_use.utils.logger import get_logger
23
+ from minitap.mobile_use.utils.video import DEFAULT_MAX_DURATION_SECONDS
24
+
25
+ logger = get_logger(__name__)
26
+
27
+
28
+ def get_start_video_recording_tool(ctx: MobileUseContext) -> BaseTool:
29
+ @tool
30
+ async def start_video_recording(
31
+ agent_thought: str,
32
+ tool_call_id: Annotated[str, InjectedToolCallId],
33
+ state: Annotated[State, InjectedState],
34
+ ):
35
+ """
36
+ Starts a background screen recording on the mobile device.
37
+ Recording continues until stop_video_recording is called.
38
+ Max duration: 3 min (Android) / 15 min (iOS). Audio is not captured.
39
+ """
40
+ platform = ctx.device.mobile_platform
41
+ controller = get_controller(ctx)
42
+
43
+ if platform in (DevicePlatform.ANDROID, DevicePlatform.IOS):
44
+ result = await controller.start_video_recording(DEFAULT_MAX_DURATION_SECONDS)
45
+ else:
46
+ from minitap.mobile_use.utils.video import VideoRecordingResult
47
+
48
+ result = VideoRecordingResult(
49
+ success=False,
50
+ message=f"Unsupported platform: {platform}",
51
+ )
52
+
53
+ if result.success:
54
+ agent_outcome = start_video_recording_wrapper.on_success_fn(result.message)
55
+ else:
56
+ agent_outcome = start_video_recording_wrapper.on_failure_fn(result.message)
57
+
58
+ tool_message = ToolMessage(
59
+ tool_call_id=tool_call_id,
60
+ content=agent_outcome,
61
+ status="success" if result.success else "error",
62
+ )
63
+
64
+ return Command(
65
+ update=await state.asanitize_update(
66
+ ctx=ctx,
67
+ update={
68
+ "agents_thoughts": [agent_thought, agent_outcome],
69
+ EXECUTOR_MESSAGES_KEY: [tool_message],
70
+ },
71
+ agent="executor",
72
+ ),
73
+ )
74
+
75
+ return start_video_recording
76
+
77
+
78
+ def get_stop_video_recording_tool(ctx: MobileUseContext) -> BaseTool:
79
+ from minitap.mobile_use.agents.video_analyzer.video_analyzer import analyze_video
80
+
81
+ @tool
82
+ async def stop_video_recording(
83
+ agent_thought: str,
84
+ tool_call_id: Annotated[str, InjectedToolCallId],
85
+ state: Annotated[State, InjectedState],
86
+ prompt: str = "Describe what happened in the video.",
87
+ ):
88
+ """
89
+ Stops the current screen recording and analyzes the video content.
90
+ Use `prompt` to specify what to extract (e.g., "What happens after each 10s of the video?").
91
+ """
92
+ platform = ctx.device.mobile_platform
93
+ controller = get_controller(ctx)
94
+
95
+ if platform in (DevicePlatform.ANDROID, DevicePlatform.IOS):
96
+ result = await controller.stop_video_recording()
97
+ else:
98
+ from minitap.mobile_use.utils.video import VideoRecordingResult
99
+
100
+ result = VideoRecordingResult(
101
+ success=False,
102
+ message=f"Unsupported platform: {platform}",
103
+ )
104
+
105
+ if not result.success or result.video_path is None:
106
+ agent_outcome = stop_video_recording_wrapper.on_failure_fn(result.message)
107
+ tool_message = ToolMessage(
108
+ tool_call_id=tool_call_id,
109
+ content=agent_outcome,
110
+ status="error",
111
+ )
112
+ return Command(
113
+ update=await state.asanitize_update(
114
+ ctx=ctx,
115
+ update={
116
+ "agents_thoughts": [agent_thought, agent_outcome],
117
+ EXECUTOR_MESSAGES_KEY: [tool_message],
118
+ },
119
+ agent="executor",
120
+ ),
121
+ )
122
+
123
+ video_path = result.video_path
124
+ try:
125
+ analysis_result = await analyze_video(
126
+ ctx=ctx,
127
+ video_path=video_path,
128
+ prompt=prompt,
129
+ )
130
+ agent_outcome = stop_video_recording_wrapper.on_success_fn(analysis_result)
131
+ status = "success"
132
+ except Exception as e:
133
+ logger.error(f"Video analysis failed: {e}")
134
+ agent_outcome = stop_video_recording_wrapper.on_failure_fn(
135
+ f"Recording stopped but analysis failed: {e}"
136
+ )
137
+ status = "error"
138
+ finally:
139
+ try:
140
+ if video_path and video_path.exists():
141
+ video_path.unlink()
142
+ if video_path.parent.exists():
143
+ shutil.rmtree(video_path.parent)
144
+ except Exception:
145
+ pass
146
+
147
+ tool_message = ToolMessage(
148
+ tool_call_id=tool_call_id,
149
+ content=agent_outcome,
150
+ status=status,
151
+ )
152
+
153
+ return Command(
154
+ update=await state.asanitize_update(
155
+ ctx=ctx,
156
+ update={
157
+ "agents_thoughts": [agent_thought, agent_outcome],
158
+ EXECUTOR_MESSAGES_KEY: [tool_message],
159
+ },
160
+ agent="executor",
161
+ ),
162
+ )
163
+
164
+ return stop_video_recording
165
+
166
+
167
+ start_video_recording_wrapper = ToolWrapper(
168
+ tool_fn_getter=get_start_video_recording_tool,
169
+ on_success_fn=lambda message: f"Video recording started successfully. {message}",
170
+ on_failure_fn=lambda message: f"Failed to start video recording: {message}",
171
+ )
172
+
173
+ stop_video_recording_wrapper = ToolWrapper(
174
+ tool_fn_getter=get_stop_video_recording_tool,
175
+ on_success_fn=lambda analysis: f"Video stopped successfully. Analysis result:\n{analysis}",
176
+ on_failure_fn=lambda message: f"Video recording/analysis failed: {message}",
177
+ )
@@ -0,0 +1,81 @@
1
+ import time
2
+ from typing import Annotated
3
+
4
+ from langchain_core.messages import ToolMessage
5
+ from langchain_core.tools import tool
6
+ from langchain_core.tools.base import InjectedToolCallId
7
+ from langgraph.prebuilt import InjectedState
8
+ from langgraph.types import Command
9
+
10
+ from minitap.mobile_use.constants import EXECUTOR_MESSAGES_KEY
11
+ from minitap.mobile_use.context import MobileUseContext
12
+ from minitap.mobile_use.graph.state import State
13
+ from minitap.mobile_use.tools.tool_wrapper import ToolWrapper
14
+
15
+ MAX_DELAY_MS = 60000
16
+
17
+
18
+ def get_wait_for_delay_tool(ctx: MobileUseContext):
19
+ @tool
20
+ async def wait_for_delay(
21
+ agent_thought: str,
22
+ time_in_ms: int,
23
+ tool_call_id: Annotated[str, InjectedToolCallId],
24
+ state: Annotated[State, InjectedState],
25
+ ) -> Command:
26
+ """
27
+ Wait for a delay in milliseconds.
28
+
29
+ This tool pauses execution for a specified number of milliseconds.
30
+ Use this when you need to introduce a controlled delay to allow the UI
31
+ to update after an action, regardless of whether an animation is playing.
32
+
33
+ Args:
34
+ time_in_ms: The number of milliseconds to wait. (capped at 60 seconds)
35
+
36
+
37
+ Example:
38
+ - wait_for_delay with time_in_ms=1000 (waits 1 second)
39
+ - wait_for_delay with time_in_ms=500 (waits 0.5 seconds)
40
+ """
41
+ if time_in_ms < 0:
42
+ time_in_ms = 1000
43
+ if time_in_ms > MAX_DELAY_MS:
44
+ time_in_ms = MAX_DELAY_MS
45
+ try:
46
+ time.sleep(time_in_ms / 1000)
47
+ output = None
48
+ has_failed = False
49
+ except Exception as e:
50
+ output = str(e)
51
+ has_failed = True
52
+ agent_outcome = (
53
+ wait_for_delay_wrapper.on_failure_fn()
54
+ if has_failed
55
+ else wait_for_delay_wrapper.on_success_fn(time_in_ms)
56
+ )
57
+ tool_message = ToolMessage(
58
+ tool_call_id=tool_call_id,
59
+ content=agent_outcome,
60
+ additional_kwargs={"error": output} if has_failed else {},
61
+ status="error" if has_failed else "success",
62
+ )
63
+ return Command(
64
+ update=await state.asanitize_update(
65
+ ctx=ctx,
66
+ update={
67
+ "agents_thoughts": [agent_thought, agent_outcome],
68
+ EXECUTOR_MESSAGES_KEY: [tool_message],
69
+ },
70
+ agent="executor",
71
+ ),
72
+ )
73
+
74
+ return wait_for_delay
75
+
76
+
77
+ wait_for_delay_wrapper = ToolWrapper(
78
+ tool_fn_getter=get_wait_for_delay_tool,
79
+ on_success_fn=lambda delay: f"Successfully waited for {delay} milliseconds.",
80
+ on_failure_fn=lambda: "Failed to wait for delay.",
81
+ )
@@ -0,0 +1,147 @@
1
+ from typing import Annotated
2
+
3
+ from langchain_core.messages import ToolMessage
4
+ from langchain_core.tools import tool
5
+ from langchain_core.tools.base import BaseTool, InjectedToolCallId
6
+ from langgraph.prebuilt import InjectedState
7
+ from langgraph.types import Command
8
+
9
+ from minitap.mobile_use.constants import EXECUTOR_MESSAGES_KEY
10
+ from minitap.mobile_use.context import MobileUseContext
11
+ from minitap.mobile_use.graph.state import State
12
+ from minitap.mobile_use.tools.tool_wrapper import ToolWrapper
13
+ from minitap.mobile_use.utils.logger import get_logger
14
+
15
+ logger = get_logger(__name__)
16
+
17
+
18
+ def get_save_note_tool(ctx: MobileUseContext) -> BaseTool:
19
+ @tool
20
+ async def save_note(
21
+ agent_thought: str,
22
+ key: str,
23
+ content: str,
24
+ tool_call_id: Annotated[str, InjectedToolCallId],
25
+ state: Annotated[State, InjectedState],
26
+ ):
27
+ """
28
+ Saves a text note to persistent memory with the given key.
29
+ If the key already exists, it will be overwritten.
30
+ """
31
+ updated_scratchpad = {**state.scratchpad, key: content}
32
+
33
+ agent_outcome = save_note_wrapper.on_success_fn(key)
34
+
35
+ tool_message = ToolMessage(
36
+ tool_call_id=tool_call_id,
37
+ content=agent_outcome,
38
+ status="success",
39
+ )
40
+ return Command(
41
+ update=await state.asanitize_update(
42
+ ctx=ctx,
43
+ update={
44
+ "agents_thoughts": [agent_thought, agent_outcome],
45
+ EXECUTOR_MESSAGES_KEY: [tool_message],
46
+ "scratchpad": updated_scratchpad,
47
+ },
48
+ agent="executor",
49
+ ),
50
+ )
51
+
52
+ return save_note
53
+
54
+
55
+ def get_read_note_tool(ctx: MobileUseContext) -> BaseTool:
56
+ @tool
57
+ async def read_note(
58
+ agent_thought: str,
59
+ key: str,
60
+ tool_call_id: Annotated[str, InjectedToolCallId],
61
+ state: Annotated[State, InjectedState],
62
+ ):
63
+ """
64
+ Reads a previously saved note from persistent memory by its key.
65
+ """
66
+ content = state.scratchpad.get(key)
67
+
68
+ if content is not None:
69
+ agent_outcome = read_note_wrapper.on_success_fn(key, content)
70
+ status = "success"
71
+ else:
72
+ agent_outcome = read_note_wrapper.on_failure_fn(key)
73
+ status = "error"
74
+
75
+ tool_message = ToolMessage(
76
+ tool_call_id=tool_call_id,
77
+ content=agent_outcome,
78
+ status=status,
79
+ )
80
+ return Command(
81
+ update=await state.asanitize_update(
82
+ ctx=ctx,
83
+ update={
84
+ "agents_thoughts": [agent_thought, agent_outcome],
85
+ EXECUTOR_MESSAGES_KEY: [tool_message],
86
+ },
87
+ agent="executor",
88
+ ),
89
+ )
90
+
91
+ return read_note
92
+
93
+
94
+ def get_list_notes_tool(ctx: MobileUseContext) -> BaseTool:
95
+ @tool
96
+ async def list_notes(
97
+ agent_thought: str,
98
+ tool_call_id: Annotated[str, InjectedToolCallId],
99
+ state: Annotated[State, InjectedState],
100
+ ):
101
+ """
102
+ Lists all note keys currently stored in persistent memory.
103
+ """
104
+ keys = list(state.scratchpad.keys())
105
+
106
+ agent_outcome = list_notes_wrapper.on_success_fn(keys)
107
+
108
+ tool_message = ToolMessage(
109
+ tool_call_id=tool_call_id,
110
+ content=agent_outcome,
111
+ status="success",
112
+ )
113
+ return Command(
114
+ update=await state.asanitize_update(
115
+ ctx=ctx,
116
+ update={
117
+ "agents_thoughts": [agent_thought, agent_outcome],
118
+ EXECUTOR_MESSAGES_KEY: [tool_message],
119
+ },
120
+ agent="executor",
121
+ ),
122
+ )
123
+
124
+ return list_notes
125
+
126
+
127
+ save_note_wrapper = ToolWrapper(
128
+ tool_fn_getter=get_save_note_tool,
129
+ on_success_fn=lambda key: (f"Successfully saved note '{key}'."),
130
+ on_failure_fn=lambda key: f"Failed to save note '{key}'.",
131
+ )
132
+
133
+ read_note_wrapper = ToolWrapper(
134
+ tool_fn_getter=get_read_note_tool,
135
+ on_success_fn=lambda key, content: (
136
+ f"Successfully read note '{key}'. '{key}' note full content: {content}"
137
+ ),
138
+ on_failure_fn=lambda key: f"Note '{key}' not found in scratchpad.",
139
+ )
140
+
141
+ list_notes_wrapper = ToolWrapper(
142
+ tool_fn_getter=get_list_notes_tool,
143
+ on_success_fn=lambda keys: (
144
+ f"Here are all the note keys: {keys}" if keys else "No notes saved yet."
145
+ ),
146
+ on_failure_fn=lambda: "Failed to list notes.",
147
+ )