minitap-mobile-use 0.0.1.dev0__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/__init__.py +0 -0
- minitap/mobile_use/agents/contextor/contextor.py +42 -0
- minitap/mobile_use/agents/cortex/cortex.md +93 -0
- minitap/mobile_use/agents/cortex/cortex.py +107 -0
- minitap/mobile_use/agents/cortex/types.py +11 -0
- minitap/mobile_use/agents/executor/executor.md +73 -0
- minitap/mobile_use/agents/executor/executor.py +84 -0
- minitap/mobile_use/agents/executor/executor_context_cleaner.py +27 -0
- minitap/mobile_use/agents/executor/utils.py +11 -0
- minitap/mobile_use/agents/hopper/hopper.md +13 -0
- minitap/mobile_use/agents/hopper/hopper.py +45 -0
- minitap/mobile_use/agents/orchestrator/human.md +13 -0
- minitap/mobile_use/agents/orchestrator/orchestrator.md +18 -0
- minitap/mobile_use/agents/orchestrator/orchestrator.py +114 -0
- minitap/mobile_use/agents/orchestrator/types.py +14 -0
- minitap/mobile_use/agents/outputter/human.md +25 -0
- minitap/mobile_use/agents/outputter/outputter.py +75 -0
- minitap/mobile_use/agents/outputter/test_outputter.py +107 -0
- minitap/mobile_use/agents/planner/human.md +12 -0
- minitap/mobile_use/agents/planner/planner.md +64 -0
- minitap/mobile_use/agents/planner/planner.py +64 -0
- minitap/mobile_use/agents/planner/types.py +44 -0
- minitap/mobile_use/agents/planner/utils.py +45 -0
- minitap/mobile_use/agents/summarizer/summarizer.py +34 -0
- minitap/mobile_use/clients/device_hardware_client.py +23 -0
- minitap/mobile_use/clients/ios_client.py +44 -0
- minitap/mobile_use/clients/screen_api_client.py +53 -0
- minitap/mobile_use/config.py +285 -0
- minitap/mobile_use/constants.py +2 -0
- minitap/mobile_use/context.py +65 -0
- minitap/mobile_use/controllers/__init__.py +0 -0
- minitap/mobile_use/controllers/mobile_command_controller.py +379 -0
- minitap/mobile_use/controllers/platform_specific_commands_controller.py +74 -0
- minitap/mobile_use/graph/graph.py +149 -0
- minitap/mobile_use/graph/state.py +73 -0
- minitap/mobile_use/main.py +122 -0
- minitap/mobile_use/sdk/__init__.py +12 -0
- minitap/mobile_use/sdk/agent.py +524 -0
- minitap/mobile_use/sdk/builders/__init__.py +10 -0
- minitap/mobile_use/sdk/builders/agent_config_builder.py +213 -0
- minitap/mobile_use/sdk/builders/index.py +15 -0
- minitap/mobile_use/sdk/builders/task_request_builder.py +218 -0
- minitap/mobile_use/sdk/constants.py +14 -0
- minitap/mobile_use/sdk/examples/README.md +45 -0
- minitap/mobile_use/sdk/examples/__init__.py +1 -0
- minitap/mobile_use/sdk/examples/simple_photo_organizer.py +76 -0
- minitap/mobile_use/sdk/examples/smart_notification_assistant.py +177 -0
- minitap/mobile_use/sdk/types/__init__.py +49 -0
- minitap/mobile_use/sdk/types/agent.py +73 -0
- minitap/mobile_use/sdk/types/exceptions.py +74 -0
- minitap/mobile_use/sdk/types/task.py +191 -0
- minitap/mobile_use/sdk/utils.py +28 -0
- minitap/mobile_use/servers/config.py +19 -0
- minitap/mobile_use/servers/device_hardware_bridge.py +212 -0
- minitap/mobile_use/servers/device_screen_api.py +143 -0
- minitap/mobile_use/servers/start_servers.py +151 -0
- minitap/mobile_use/servers/stop_servers.py +215 -0
- minitap/mobile_use/servers/utils.py +11 -0
- minitap/mobile_use/services/accessibility.py +100 -0
- minitap/mobile_use/services/llm.py +143 -0
- minitap/mobile_use/tools/index.py +54 -0
- minitap/mobile_use/tools/mobile/back.py +52 -0
- minitap/mobile_use/tools/mobile/copy_text_from.py +77 -0
- minitap/mobile_use/tools/mobile/erase_text.py +124 -0
- minitap/mobile_use/tools/mobile/input_text.py +74 -0
- minitap/mobile_use/tools/mobile/launch_app.py +59 -0
- minitap/mobile_use/tools/mobile/list_packages.py +78 -0
- minitap/mobile_use/tools/mobile/long_press_on.py +62 -0
- minitap/mobile_use/tools/mobile/open_link.py +59 -0
- minitap/mobile_use/tools/mobile/paste_text.py +66 -0
- minitap/mobile_use/tools/mobile/press_key.py +58 -0
- minitap/mobile_use/tools/mobile/run_flow.py +57 -0
- minitap/mobile_use/tools/mobile/stop_app.py +58 -0
- minitap/mobile_use/tools/mobile/swipe.py +56 -0
- minitap/mobile_use/tools/mobile/take_screenshot.py +70 -0
- minitap/mobile_use/tools/mobile/tap.py +66 -0
- minitap/mobile_use/tools/mobile/wait_for_animation_to_end.py +68 -0
- minitap/mobile_use/tools/tool_wrapper.py +33 -0
- minitap/mobile_use/utils/cli_helpers.py +40 -0
- minitap/mobile_use/utils/cli_selection.py +144 -0
- minitap/mobile_use/utils/conversations.py +31 -0
- minitap/mobile_use/utils/decorators.py +123 -0
- minitap/mobile_use/utils/errors.py +6 -0
- minitap/mobile_use/utils/file.py +13 -0
- minitap/mobile_use/utils/logger.py +184 -0
- minitap/mobile_use/utils/media.py +73 -0
- minitap/mobile_use/utils/recorder.py +55 -0
- minitap/mobile_use/utils/requests_utils.py +37 -0
- minitap/mobile_use/utils/shell_utils.py +20 -0
- minitap/mobile_use/utils/time.py +6 -0
- minitap/mobile_use/utils/ui_hierarchy.py +30 -0
- minitap_mobile_use-0.0.1.dev0.dist-info/METADATA +274 -0
- minitap_mobile_use-0.0.1.dev0.dist-info/RECORD +95 -0
- minitap_mobile_use-0.0.1.dev0.dist-info/WHEEL +4 -0
- minitap_mobile_use-0.0.1.dev0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Builder for AgentConfig objects using a fluent interface.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, Optional, List
|
|
6
|
+
import copy
|
|
7
|
+
|
|
8
|
+
from minitap.mobile_use.config import get_default_llm_config
|
|
9
|
+
from minitap.mobile_use.sdk.constants import (
|
|
10
|
+
DEFAULT_HW_BRIDGE_BASE_URL,
|
|
11
|
+
DEFAULT_PROFILE_NAME,
|
|
12
|
+
DEFAULT_SCREEN_API_BASE_URL,
|
|
13
|
+
)
|
|
14
|
+
from minitap.mobile_use.sdk.types.agent import ApiBaseUrl, AgentConfig, ServerConfig
|
|
15
|
+
from minitap.mobile_use.sdk.types.agent import AgentProfile
|
|
16
|
+
from minitap.mobile_use.sdk.types.task import TaskRequestCommon
|
|
17
|
+
from minitap.mobile_use.context import DevicePlatform
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AgentConfigBuilder:
|
|
21
|
+
"""
|
|
22
|
+
Builder class providing a fluent interface for creating AgentConfig objects.
|
|
23
|
+
|
|
24
|
+
This builder allows for step-by-step construction of an AgentConfig with
|
|
25
|
+
clear methods that make the configuration process intuitive and type-safe.
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
>>> builder = AgentConfigBuilder()
|
|
29
|
+
>>> config = (builder
|
|
30
|
+
... .add_profile(AgentProfile(name="HighReasoning", llm_config=LLMConfig(...)))
|
|
31
|
+
... .add_profile(AgentProfile(name="LowReasoning", llm_config=LLMConfig(...)))
|
|
32
|
+
... .for_device(DevicePlatform.ANDROID, "device123")
|
|
33
|
+
... .with_default_task_config(TaskRequestCommon(max_steps=30))
|
|
34
|
+
... .with_default_profile("HighReasoning")
|
|
35
|
+
... .build()
|
|
36
|
+
... )
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self):
|
|
40
|
+
"""Initialize an empty AgentConfigBuilder."""
|
|
41
|
+
self._agent_profiles: Dict[str, AgentProfile] = {}
|
|
42
|
+
self._task_request_defaults: Optional[TaskRequestCommon] = None
|
|
43
|
+
self._default_profile: Optional[str | AgentProfile] = None
|
|
44
|
+
self._device_id: Optional[str] = None
|
|
45
|
+
self._device_platform: Optional[DevicePlatform] = None
|
|
46
|
+
self._servers: ServerConfig = get_default_servers()
|
|
47
|
+
|
|
48
|
+
def add_profile(self, profile: AgentProfile) -> "AgentConfigBuilder":
|
|
49
|
+
"""
|
|
50
|
+
Add an agent profile to the mobile-use agent.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
profile: The agent profile to add
|
|
54
|
+
"""
|
|
55
|
+
self._agent_profiles[profile.name] = profile
|
|
56
|
+
profile.llm_config.validate_providers()
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
def add_profiles(self, profiles: List[AgentProfile]) -> "AgentConfigBuilder":
|
|
60
|
+
"""
|
|
61
|
+
Add multiple agent profiles to the mobile-use agent.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
profiles: List of agent profiles to add
|
|
65
|
+
"""
|
|
66
|
+
for profile in profiles:
|
|
67
|
+
self.add_profile(profile=profile)
|
|
68
|
+
profile.llm_config.validate_providers()
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def with_default_profile(self, profile: str | AgentProfile) -> "AgentConfigBuilder":
|
|
72
|
+
"""
|
|
73
|
+
Set the default agent profile used for tasks.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
profile: The name or instance of the default agent profile
|
|
77
|
+
"""
|
|
78
|
+
self._default_profile = profile
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
def for_device(
|
|
82
|
+
self,
|
|
83
|
+
platform: DevicePlatform,
|
|
84
|
+
device_id: str,
|
|
85
|
+
) -> "AgentConfigBuilder":
|
|
86
|
+
"""
|
|
87
|
+
Configure the mobile-use agent for a specific device.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
platform: The device platform (ANDROID or IOS)
|
|
91
|
+
device_id: The unique identifier for the device
|
|
92
|
+
"""
|
|
93
|
+
self._device_id = device_id
|
|
94
|
+
self._device_platform = platform
|
|
95
|
+
return self
|
|
96
|
+
|
|
97
|
+
def with_default_task_config(self, config: TaskRequestCommon) -> "AgentConfigBuilder":
|
|
98
|
+
"""
|
|
99
|
+
Set the default task configuration.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
config: The task configuration to use as default
|
|
103
|
+
"""
|
|
104
|
+
self._task_request_defaults = copy.deepcopy(config)
|
|
105
|
+
return self
|
|
106
|
+
|
|
107
|
+
def with_hw_bridge_base_url(self, url: str | ApiBaseUrl) -> "AgentConfigBuilder":
|
|
108
|
+
"""
|
|
109
|
+
Set the base URL for the device HW bridge API.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
url: The base URL for the HW bridge API
|
|
113
|
+
"""
|
|
114
|
+
if isinstance(url, str):
|
|
115
|
+
url = ApiBaseUrl.from_url(url)
|
|
116
|
+
self._servers.hw_bridge_base_url = url
|
|
117
|
+
return self
|
|
118
|
+
|
|
119
|
+
def with_screen_api_base_url(self, url: str | ApiBaseUrl) -> "AgentConfigBuilder":
|
|
120
|
+
"""
|
|
121
|
+
Set the base URL for the device screen API.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
url: The base URL for the screen API
|
|
125
|
+
"""
|
|
126
|
+
if isinstance(url, str):
|
|
127
|
+
url = ApiBaseUrl.from_url(url)
|
|
128
|
+
self._servers.screen_api_base_url = url
|
|
129
|
+
return self
|
|
130
|
+
|
|
131
|
+
def with_adb_server(self, host: str, port: Optional[int] = None) -> "AgentConfigBuilder":
|
|
132
|
+
"""
|
|
133
|
+
Set the ADB server host and port.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
host: The ADB server host
|
|
137
|
+
port: The ADB server port
|
|
138
|
+
"""
|
|
139
|
+
self._servers.adb_host = host
|
|
140
|
+
if port is not None:
|
|
141
|
+
self._servers.adb_port = port
|
|
142
|
+
return self
|
|
143
|
+
|
|
144
|
+
def with_servers(self, servers: ServerConfig) -> "AgentConfigBuilder":
|
|
145
|
+
"""
|
|
146
|
+
Set the server settings.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
servers: The server settings to use
|
|
150
|
+
"""
|
|
151
|
+
self._servers = copy.deepcopy(servers)
|
|
152
|
+
return self
|
|
153
|
+
|
|
154
|
+
def build(self) -> AgentConfig:
|
|
155
|
+
"""
|
|
156
|
+
Build the mobile-use AgentConfig object.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
default_profile: Name of the default agent profile to use
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
A configured AgentConfig object
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
ValueError: If default_profile is specified but not found in configured profiles
|
|
166
|
+
"""
|
|
167
|
+
nb_profiles = len(self._agent_profiles)
|
|
168
|
+
|
|
169
|
+
if isinstance(self._default_profile, str):
|
|
170
|
+
profile_name = self._default_profile
|
|
171
|
+
default_profile = self._agent_profiles.get(profile_name, None)
|
|
172
|
+
if default_profile is None:
|
|
173
|
+
raise ValueError(f"Profile '{profile_name}' not found in configured agents")
|
|
174
|
+
elif isinstance(self._default_profile, AgentProfile):
|
|
175
|
+
default_profile = self._default_profile
|
|
176
|
+
if default_profile.name not in self._agent_profiles:
|
|
177
|
+
self.add_profile(default_profile)
|
|
178
|
+
elif nb_profiles <= 0:
|
|
179
|
+
default_profile = AgentProfile(
|
|
180
|
+
name=DEFAULT_PROFILE_NAME,
|
|
181
|
+
llm_config=get_default_llm_config(),
|
|
182
|
+
)
|
|
183
|
+
self.add_profile(default_profile)
|
|
184
|
+
elif nb_profiles == 1:
|
|
185
|
+
# Select the only one available
|
|
186
|
+
default_profile = next(iter(self._agent_profiles.values()))
|
|
187
|
+
else:
|
|
188
|
+
available_profiles = ", ".join(self._agent_profiles.keys())
|
|
189
|
+
raise ValueError(
|
|
190
|
+
f"You must call with_default_profile() to select one among: {available_profiles}"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
return AgentConfig(
|
|
194
|
+
agent_profiles=self._agent_profiles,
|
|
195
|
+
task_request_defaults=self._task_request_defaults or TaskRequestCommon(),
|
|
196
|
+
default_profile=default_profile,
|
|
197
|
+
device_id=self._device_id,
|
|
198
|
+
device_platform=self._device_platform,
|
|
199
|
+
servers=self._servers,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def get_default_agent_config():
|
|
204
|
+
return AgentConfigBuilder().build()
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def get_default_servers():
|
|
208
|
+
return ServerConfig(
|
|
209
|
+
hw_bridge_base_url=copy.deepcopy(DEFAULT_HW_BRIDGE_BASE_URL),
|
|
210
|
+
screen_api_base_url=copy.deepcopy(DEFAULT_SCREEN_API_BASE_URL),
|
|
211
|
+
adb_host="localhost",
|
|
212
|
+
adb_port=5037,
|
|
213
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from minitap.mobile_use.sdk.builders.agent_config_builder import AgentConfigBuilder
|
|
2
|
+
from minitap.mobile_use.sdk.builders.task_request_builder import TaskRequestCommonBuilder
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BuildersWrapper:
|
|
6
|
+
@property
|
|
7
|
+
def AgentConfig(self) -> AgentConfigBuilder:
|
|
8
|
+
return AgentConfigBuilder()
|
|
9
|
+
|
|
10
|
+
@property
|
|
11
|
+
def TaskDefaults(self) -> TaskRequestCommonBuilder:
|
|
12
|
+
return TaskRequestCommonBuilder()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
Builders = BuildersWrapper()
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Builder for TaskRequest objects using a fluent interface.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Generic, Optional, Self, TypeVar, cast
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from minitap.mobile_use.constants import RECURSION_LIMIT
|
|
11
|
+
from minitap.mobile_use.sdk.types.agent import AgentProfile
|
|
12
|
+
from minitap.mobile_use.sdk.types.task import TaskRequest, TaskRequestCommon
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
TIn = TypeVar("TIn", bound=Optional[BaseModel])
|
|
16
|
+
TOut = TypeVar("TOut", bound=BaseModel)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TaskRequestCommonBuilder(BaseModel):
|
|
20
|
+
"""
|
|
21
|
+
Builder class providing a fluent interface for creating TaskRequestCommon objects.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self._max_steps = RECURSION_LIMIT
|
|
26
|
+
self._record_trace = False
|
|
27
|
+
self._trace_path = Path("mobile-use-traces")
|
|
28
|
+
self._llm_output_path: Optional[Path] = None
|
|
29
|
+
self._thoughts_output_path: Optional[Path] = None
|
|
30
|
+
|
|
31
|
+
def with_max_steps(self, max_steps: int) -> Self:
|
|
32
|
+
"""
|
|
33
|
+
Set the maximum number of steps the task can take.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
max_steps: Maximum number of steps
|
|
37
|
+
"""
|
|
38
|
+
self._max_steps = max_steps
|
|
39
|
+
return self
|
|
40
|
+
|
|
41
|
+
def with_trace_recording(self, enabled: bool = True, path: Optional[str] = None) -> Self:
|
|
42
|
+
"""
|
|
43
|
+
Configure trace recording for the task.
|
|
44
|
+
|
|
45
|
+
Traces record screenshots and actions during execution.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
enabled: Whether to enable trace recording
|
|
49
|
+
path: Directory path where traces should be saved
|
|
50
|
+
"""
|
|
51
|
+
self._record_trace = enabled
|
|
52
|
+
if enabled and path:
|
|
53
|
+
self._trace_path = Path(path)
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
def with_llm_output_saving(self, path: str) -> Self:
|
|
57
|
+
"""
|
|
58
|
+
Configure LLM output saving for the task.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
path: Path where to save the LLM output message
|
|
62
|
+
"""
|
|
63
|
+
self._llm_output_path = Path(path)
|
|
64
|
+
return self
|
|
65
|
+
|
|
66
|
+
def with_thoughts_output_saving(self, path: str) -> Self:
|
|
67
|
+
"""
|
|
68
|
+
Configure thoughts output saving for the task.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
path: Path where to save the thoughts output message
|
|
72
|
+
"""
|
|
73
|
+
self._thoughts_output_path = Path(path)
|
|
74
|
+
return self
|
|
75
|
+
|
|
76
|
+
def build(self) -> TaskRequestCommon:
|
|
77
|
+
"""
|
|
78
|
+
Build the TaskRequestCommon object.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
A configured TaskRequestCommon object
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
ValueError: If required fields are missing
|
|
85
|
+
"""
|
|
86
|
+
return TaskRequestCommon(
|
|
87
|
+
max_steps=self._max_steps,
|
|
88
|
+
record_trace=self._record_trace,
|
|
89
|
+
trace_path=self._trace_path,
|
|
90
|
+
llm_output_path=self._llm_output_path,
|
|
91
|
+
thoughts_output_path=self._thoughts_output_path,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class TaskRequestBuilder(TaskRequestCommonBuilder, Generic[TIn]):
|
|
96
|
+
"""
|
|
97
|
+
Builder class providing a fluent interface for creating TaskRequest objects.
|
|
98
|
+
|
|
99
|
+
This builder allows for step-by-step construction of a TaskRequest with
|
|
100
|
+
clear methods that make the configuration process intuitive and type-safe.
|
|
101
|
+
|
|
102
|
+
Examples:
|
|
103
|
+
>>> builder = TaskRequestBuilder[None](goal="Open Gmail and check unread emails")
|
|
104
|
+
>>> task_request = (
|
|
105
|
+
... builder
|
|
106
|
+
... .with_max_steps(30)
|
|
107
|
+
... .using_profile("LowReasoning")
|
|
108
|
+
... .with_output_description("A list of email subjects and senders")
|
|
109
|
+
... .build()
|
|
110
|
+
... )
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(self, goal: str):
|
|
114
|
+
"""Initialize an empty TaskRequestBuilder."""
|
|
115
|
+
super().__init__()
|
|
116
|
+
self._goal = goal
|
|
117
|
+
self._profile: Optional[str | AgentProfile] = None
|
|
118
|
+
self._name: Optional[str] = None
|
|
119
|
+
self._output_description = None
|
|
120
|
+
self._output_format: Optional[type[TIn]] = None
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def from_common(cls, goal: str, common: TaskRequestCommon):
|
|
124
|
+
res = cls(goal=goal)
|
|
125
|
+
res._max_steps = common.max_steps
|
|
126
|
+
res._record_trace = common.record_trace
|
|
127
|
+
res._trace_path = common.trace_path
|
|
128
|
+
res._llm_output_path = common.llm_output_path
|
|
129
|
+
res._thoughts_output_path = common.thoughts_output_path
|
|
130
|
+
return res
|
|
131
|
+
|
|
132
|
+
def using_profile(self, profile: str | AgentProfile) -> "TaskRequestBuilder[TIn]":
|
|
133
|
+
"""
|
|
134
|
+
Set the agent profile for executing the task.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
profile: The agent profile to use
|
|
138
|
+
"""
|
|
139
|
+
self._profile = profile
|
|
140
|
+
return self
|
|
141
|
+
|
|
142
|
+
def with_name(self, name: str) -> "TaskRequestBuilder[TIn]":
|
|
143
|
+
"""
|
|
144
|
+
Set the name of the task - useful when recording traces.
|
|
145
|
+
Otherwise, a random name will be generated.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
name: Name of the task
|
|
149
|
+
"""
|
|
150
|
+
self._name = name
|
|
151
|
+
return self
|
|
152
|
+
|
|
153
|
+
def without_llm_output_saving(self) -> Self:
|
|
154
|
+
"""
|
|
155
|
+
Disable LLM output saving for the task.
|
|
156
|
+
"""
|
|
157
|
+
self._llm_output_path = None
|
|
158
|
+
return self
|
|
159
|
+
|
|
160
|
+
def without_thoughts_output_saving(self):
|
|
161
|
+
"""
|
|
162
|
+
Disable thoughts output saving for the task.
|
|
163
|
+
"""
|
|
164
|
+
self._thoughts_output_path = None
|
|
165
|
+
return self
|
|
166
|
+
|
|
167
|
+
def with_output_description(self, description: str) -> "TaskRequestBuilder[TIn]":
|
|
168
|
+
"""
|
|
169
|
+
Set the description of the expected output format.
|
|
170
|
+
This is especially useful for data extraction tasks.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
description: Description of the expected output format
|
|
174
|
+
"""
|
|
175
|
+
self._output_description = description
|
|
176
|
+
return self
|
|
177
|
+
|
|
178
|
+
def with_output_format(self, output_format: type[TOut]) -> "TaskRequestBuilder[TOut]":
|
|
179
|
+
"""
|
|
180
|
+
Set the pydantic model for the expected output format.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
output_format: Pydantic model instance defining the output format
|
|
184
|
+
"""
|
|
185
|
+
self._output_format = output_format # type: ignore
|
|
186
|
+
return cast(TaskRequestBuilder[TOut], self)
|
|
187
|
+
|
|
188
|
+
def build(self) -> TaskRequest[TIn]:
|
|
189
|
+
"""
|
|
190
|
+
Build the TaskRequest object.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
A configured TaskRequest object
|
|
194
|
+
|
|
195
|
+
Raises:
|
|
196
|
+
ValueError: If required fields are missing
|
|
197
|
+
"""
|
|
198
|
+
if not self._goal:
|
|
199
|
+
raise ValueError("Task goal is required")
|
|
200
|
+
|
|
201
|
+
if self._output_format and self._output_description:
|
|
202
|
+
raise ValueError("Output format and description are mutually exclusive")
|
|
203
|
+
|
|
204
|
+
task_request = TaskRequest(
|
|
205
|
+
goal=self._goal,
|
|
206
|
+
profile=self._profile.name
|
|
207
|
+
if isinstance(self._profile, AgentProfile)
|
|
208
|
+
else self._profile,
|
|
209
|
+
task_name=self._name,
|
|
210
|
+
output_description=self._output_description,
|
|
211
|
+
output_format=self._output_format,
|
|
212
|
+
max_steps=self._max_steps,
|
|
213
|
+
record_trace=self._record_trace,
|
|
214
|
+
trace_path=self._trace_path,
|
|
215
|
+
llm_output_path=self._llm_output_path,
|
|
216
|
+
thoughts_output_path=self._thoughts_output_path,
|
|
217
|
+
)
|
|
218
|
+
return task_request
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from minitap.mobile_use.sdk.types.agent import ApiBaseUrl
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
DEFAULT_PROFILE_NAME = "default"
|
|
5
|
+
DEFAULT_HW_BRIDGE_BASE_URL = ApiBaseUrl(
|
|
6
|
+
scheme="http",
|
|
7
|
+
host="localhost",
|
|
8
|
+
port=9999,
|
|
9
|
+
)
|
|
10
|
+
DEFAULT_SCREEN_API_BASE_URL = ApiBaseUrl(
|
|
11
|
+
scheme="http",
|
|
12
|
+
host="localhost",
|
|
13
|
+
port=9998,
|
|
14
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# mobile-use SDK Examples
|
|
2
|
+
|
|
3
|
+
Location: `src/mobile_use/sdk/examples/`
|
|
4
|
+
|
|
5
|
+
Run any example via:
|
|
6
|
+
- `python src/mobile_use/sdk/examples/<filename>.py`
|
|
7
|
+
|
|
8
|
+
## Practical Automation Examples
|
|
9
|
+
|
|
10
|
+
These examples demonstrate two different ways to use the SDK, each applying an appropriate level of complexity for the task at hand:
|
|
11
|
+
|
|
12
|
+
### simple_photo_organizer.py - Straightforward Approach
|
|
13
|
+
|
|
14
|
+
Demonstrates the simplest way to use the SDK for quick automation tasks:
|
|
15
|
+
|
|
16
|
+
- **Direct API calls** without builders or complex configuration
|
|
17
|
+
- Creates a photo album and organizes photos from a specific date
|
|
18
|
+
- Uses structured Pydantic output to capture results
|
|
19
|
+
|
|
20
|
+
### smart_notification_assistant.py - Feature-Rich Approach
|
|
21
|
+
|
|
22
|
+
Showcases more advanced SDK features while remaining practical:
|
|
23
|
+
|
|
24
|
+
- Uses builder pattern for configuring the agent and overriding the default task configurations
|
|
25
|
+
- Implements **multiple specialized agent profiles** for different reasoning tasks:
|
|
26
|
+
- Analyzer profile for detailed inspection of notifications
|
|
27
|
+
- Note taker profile for writing a summary of the notifications
|
|
28
|
+
- Enables **tracing** for debugging and visualization
|
|
29
|
+
- Includes **structured Pydantic models** with enums and nested relationships
|
|
30
|
+
- Demonstrates proper **exception handling** for different error types
|
|
31
|
+
- Shows how to set up task defaults for consistent configuration
|
|
32
|
+
|
|
33
|
+
## Usage Notes
|
|
34
|
+
|
|
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.
|
|
36
|
+
|
|
37
|
+
- **Device Detection**: The agent detects the first available device unless you specify one with `AgentConfigBuilder.for_device(...)`.
|
|
38
|
+
|
|
39
|
+
- **Servers**: With default base URLs (`localhost:9998/9999`), the agent starts the servers automatically. When you override URLs, it assumes servers are already running.
|
|
40
|
+
|
|
41
|
+
- **LLM API Keys**: Provide necessary keys (e.g., `OPENAI_API_KEY`) in a `.env` file at repo root; see `mobile_use/config.py`.
|
|
42
|
+
|
|
43
|
+
- **Traces**: When enabled, traces are saved to a specified directory (defaulting to `./mobile-use-traces/`) and can be useful for debugging and visualization.
|
|
44
|
+
|
|
45
|
+
- **Structured Output**: Pydantic models enable type safety when processing task outputs, making it easier to handle and chain results between tasks.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Example scripts for the mobile-use SDK."""
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Simple Photo Organizer - Basic SDK Usage Example
|
|
3
|
+
|
|
4
|
+
This example demonstrates a straightforward way to use the mobile-use SDK
|
|
5
|
+
without builders or advanced configuration. It performs a real-world automation task:
|
|
6
|
+
1. Opens the photo gallery
|
|
7
|
+
2. Finds photos from a specific date
|
|
8
|
+
3. Creates an album and moves those photos into it
|
|
9
|
+
|
|
10
|
+
Run:
|
|
11
|
+
- python src/mobile_use/sdk/examples/simple_photo_organizer.py
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
from datetime import date, timedelta
|
|
16
|
+
from pydantic import BaseModel, Field
|
|
17
|
+
from minitap.mobile_use.sdk import Agent
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PhotosResult(BaseModel):
|
|
21
|
+
"""Structured result from photo search."""
|
|
22
|
+
|
|
23
|
+
found_photos: int = Field(..., description="Number of photos found")
|
|
24
|
+
date_range: str = Field(..., description="Date range of photos found")
|
|
25
|
+
album_created: bool = Field(..., description="Whether an album was created")
|
|
26
|
+
album_name: str = Field(..., description="Name of the created album")
|
|
27
|
+
photos_moved: int = Field(0, description="Number of photos moved to the album")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def main() -> None:
|
|
31
|
+
# Create a simple agent with default configuration
|
|
32
|
+
agent = Agent()
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
# Initialize agent (finds a device, starts required servers)
|
|
36
|
+
agent.init()
|
|
37
|
+
|
|
38
|
+
# Calculate yesterday's date for the example
|
|
39
|
+
yesterday = date.today() - timedelta(days=1)
|
|
40
|
+
formatted_date = yesterday.strftime("%B %d") # e.g. "August 22"
|
|
41
|
+
|
|
42
|
+
print(f"Looking for photos from {formatted_date}...")
|
|
43
|
+
|
|
44
|
+
# First task: search for photos and organize them, with typed output
|
|
45
|
+
result = await agent.run_task(
|
|
46
|
+
goal=(
|
|
47
|
+
f"Open the Photos/Gallery app. Find photos taken on {formatted_date}. "
|
|
48
|
+
f"Create a new album named '{formatted_date} Memories' and "
|
|
49
|
+
f"move those photos into it. Count how many photos were moved."
|
|
50
|
+
),
|
|
51
|
+
output=PhotosResult,
|
|
52
|
+
name="organize_photos",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Handle and display the result
|
|
56
|
+
if result:
|
|
57
|
+
print("\n=== Photo Organization Complete ===")
|
|
58
|
+
print(f"Found: {result.found_photos} photos from {result.date_range}")
|
|
59
|
+
|
|
60
|
+
if result.album_created:
|
|
61
|
+
print(f"Created album: '{result.album_name}'")
|
|
62
|
+
print(f"Moved {result.photos_moved} photos to the album")
|
|
63
|
+
else:
|
|
64
|
+
print("No album was created")
|
|
65
|
+
else:
|
|
66
|
+
print("Failed to organize photos")
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
print(f"Error: {e}")
|
|
70
|
+
finally:
|
|
71
|
+
# Always clean up resources
|
|
72
|
+
agent.clean()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
asyncio.run(main())
|