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,177 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Smart Notification Assistant - Intermediate SDK Usage Example
|
|
3
|
+
|
|
4
|
+
This example demonstrates more advanced SDK features including:
|
|
5
|
+
- TaskRequestBuilder pattern
|
|
6
|
+
- Multiple agent profiles for different reasoning tasks
|
|
7
|
+
- Tracing for debugging/visualization
|
|
8
|
+
- Structured output with Pydantic
|
|
9
|
+
- Exception handling
|
|
10
|
+
|
|
11
|
+
It performs a practical automation task:
|
|
12
|
+
1. Checks notification panel for unread notifications
|
|
13
|
+
2. Categorizes them by priority/app
|
|
14
|
+
3. Performs actions based on notification content
|
|
15
|
+
|
|
16
|
+
Run:
|
|
17
|
+
- python src/mobile_use/sdk/examples/smart_notification_assistant.py
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import asyncio
|
|
21
|
+
from datetime import datetime
|
|
22
|
+
from enum import Enum
|
|
23
|
+
from typing import List
|
|
24
|
+
|
|
25
|
+
from pydantic import BaseModel, Field
|
|
26
|
+
from minitap.mobile_use.config import LLM, LLMConfig, LLMConfigUtils, LLMWithFallback
|
|
27
|
+
from minitap.mobile_use.sdk import Agent
|
|
28
|
+
from minitap.mobile_use.sdk.builders import Builders
|
|
29
|
+
from minitap.mobile_use.sdk.types import AgentProfile
|
|
30
|
+
from minitap.mobile_use.sdk.types.exceptions import AgentError
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class NotificationPriority(str, Enum):
|
|
34
|
+
HIGH = "high"
|
|
35
|
+
MEDIUM = "medium"
|
|
36
|
+
LOW = "low"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Notification(BaseModel):
|
|
40
|
+
"""Individual notification details."""
|
|
41
|
+
|
|
42
|
+
app_name: str = Field(..., description="Name of the app that sent the notification")
|
|
43
|
+
title: str = Field(..., description="Title/header of the notification")
|
|
44
|
+
message: str = Field(..., description="Message content of the notification")
|
|
45
|
+
priority: NotificationPriority = Field(
|
|
46
|
+
default=NotificationPriority.MEDIUM, description="Priority level of notification"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class NotificationSummary(BaseModel):
|
|
51
|
+
"""Summary of all notifications."""
|
|
52
|
+
|
|
53
|
+
total_count: int = Field(..., description="Total number of notifications found")
|
|
54
|
+
high_priority_count: int = Field(0, description="Count of high priority notifications")
|
|
55
|
+
notifications: List[Notification] = Field(
|
|
56
|
+
default_factory=list, description="List of individual notifications"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_agent() -> Agent:
|
|
61
|
+
# Create two specialized profiles:
|
|
62
|
+
# 1. An analyzer profile for detailed inspection tasks
|
|
63
|
+
analyzer_profile = AgentProfile(
|
|
64
|
+
name="analyzer",
|
|
65
|
+
llm_config=LLMConfig(
|
|
66
|
+
planner=LLM(provider="openrouter", model="meta-llama/llama-4-scout"),
|
|
67
|
+
orchestrator=LLM(provider="openrouter", model="meta-llama/llama-4-scout"),
|
|
68
|
+
cortex=LLMWithFallback(
|
|
69
|
+
provider="openai",
|
|
70
|
+
model="o4-mini",
|
|
71
|
+
fallback=LLM(provider="openai", model="gpt-5"),
|
|
72
|
+
),
|
|
73
|
+
executor=LLM(provider="openai", model="gpt-5-nano"),
|
|
74
|
+
utils=LLMConfigUtils(
|
|
75
|
+
outputter=LLM(provider="openai", model="gpt-5-nano"),
|
|
76
|
+
hopper=LLM(provider="openai", model="gpt-4.1"),
|
|
77
|
+
),
|
|
78
|
+
),
|
|
79
|
+
# from_file="/tmp/analyzer.jsonc" # can be loaded from file
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# 2. An action profile for handling easy & fast actions based on notifications
|
|
83
|
+
action_profile = AgentProfile(
|
|
84
|
+
name="note_taker",
|
|
85
|
+
llm_config=LLMConfig(
|
|
86
|
+
planner=LLM(provider="openai", model="o3"),
|
|
87
|
+
orchestrator=LLM(provider="google", model="gemini-2.5-flash"),
|
|
88
|
+
cortex=LLMWithFallback(
|
|
89
|
+
provider="openai",
|
|
90
|
+
model="o4-mini",
|
|
91
|
+
fallback=LLM(provider="openai", model="gpt-5"),
|
|
92
|
+
),
|
|
93
|
+
executor=LLM(provider="openai", model="gpt-4o-mini"),
|
|
94
|
+
utils=LLMConfigUtils(
|
|
95
|
+
outputter=LLM(provider="openai", model="gpt-5-nano"),
|
|
96
|
+
hopper=LLM(provider="openai", model="gpt-4.1"),
|
|
97
|
+
),
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Configure default task settings with tracing
|
|
102
|
+
task_defaults = Builders.TaskDefaults.with_max_steps(200).build()
|
|
103
|
+
|
|
104
|
+
# Configure the agent
|
|
105
|
+
config = (
|
|
106
|
+
Builders.AgentConfig.add_profiles(profiles=[analyzer_profile, action_profile])
|
|
107
|
+
.with_default_profile(profile=action_profile)
|
|
108
|
+
.with_default_task_config(config=task_defaults)
|
|
109
|
+
.build()
|
|
110
|
+
)
|
|
111
|
+
return Agent(config=config)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
async def main():
|
|
115
|
+
# Set up traces directory with timestamp for uniqueness
|
|
116
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M")
|
|
117
|
+
traces_dir = f"/tmp/notification_traces/{timestamp}"
|
|
118
|
+
agent = get_agent()
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
# Initialize agent (finds a device, starts required servers)
|
|
122
|
+
agent.init()
|
|
123
|
+
|
|
124
|
+
print("Checking for notifications...")
|
|
125
|
+
|
|
126
|
+
# Task 1: Get and analyze notifications with analyzer profile
|
|
127
|
+
notification_task = (
|
|
128
|
+
agent.new_task(
|
|
129
|
+
goal="Open the notification panel (swipe down from top). "
|
|
130
|
+
"Scroll through the first 3 unread notifications. "
|
|
131
|
+
"For each notification, identify the app name, title, and content. "
|
|
132
|
+
"Tag messages from messaging apps or email as high priority."
|
|
133
|
+
)
|
|
134
|
+
.with_output_format(NotificationSummary)
|
|
135
|
+
.using_profile("analyzer")
|
|
136
|
+
.with_name("notification_scan")
|
|
137
|
+
.with_max_steps(400)
|
|
138
|
+
.with_trace_recording(enabled=True, path=traces_dir)
|
|
139
|
+
.build()
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Execute the task with proper exception handling
|
|
143
|
+
try:
|
|
144
|
+
notifications = await agent.run_task(request=notification_task)
|
|
145
|
+
|
|
146
|
+
# Display the structured results
|
|
147
|
+
if notifications:
|
|
148
|
+
print("\n=== Notification Summary ===")
|
|
149
|
+
print(f"Total notifications: {notifications.total_count}")
|
|
150
|
+
print(f"High priority: {notifications.high_priority_count}")
|
|
151
|
+
|
|
152
|
+
# Task 2: Create a note to store the notification summary
|
|
153
|
+
response = await agent.run_task(
|
|
154
|
+
goal="Open my Notes app and create a new note summarizing the following "
|
|
155
|
+
f"information:\n{notifications}",
|
|
156
|
+
name="email_action",
|
|
157
|
+
profile="note_taker",
|
|
158
|
+
)
|
|
159
|
+
print(f"Action result: {response}")
|
|
160
|
+
|
|
161
|
+
else:
|
|
162
|
+
print("Failed to retrieve notifications")
|
|
163
|
+
|
|
164
|
+
except AgentError as e:
|
|
165
|
+
print(f"Agent error occurred: {e}")
|
|
166
|
+
except Exception as e:
|
|
167
|
+
print(f"Unexpected error: {type(e).__name__}: {e}")
|
|
168
|
+
raise
|
|
169
|
+
|
|
170
|
+
finally:
|
|
171
|
+
# Clean up
|
|
172
|
+
agent.clean()
|
|
173
|
+
print(f"\nTraces saved to: {traces_dir}")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if __name__ == "__main__":
|
|
177
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Type definitions for the mobile-use SDK."""
|
|
2
|
+
|
|
3
|
+
from minitap.mobile_use.sdk.types.agent import (
|
|
4
|
+
ApiBaseUrl,
|
|
5
|
+
AgentConfig,
|
|
6
|
+
DevicePlatform,
|
|
7
|
+
ServerConfig,
|
|
8
|
+
)
|
|
9
|
+
from minitap.mobile_use.sdk.types.task import (
|
|
10
|
+
AgentProfile,
|
|
11
|
+
TaskRequest,
|
|
12
|
+
TaskStatus,
|
|
13
|
+
TaskResult,
|
|
14
|
+
TaskRequestCommon,
|
|
15
|
+
Task,
|
|
16
|
+
)
|
|
17
|
+
from minitap.mobile_use.sdk.types.exceptions import (
|
|
18
|
+
AgentProfileNotFoundError,
|
|
19
|
+
AgentTaskRequestError,
|
|
20
|
+
DeviceNotFoundError,
|
|
21
|
+
ServerStartupError,
|
|
22
|
+
AgentError,
|
|
23
|
+
AgentNotInitializedError,
|
|
24
|
+
DeviceError,
|
|
25
|
+
MobileUseError,
|
|
26
|
+
ServerError,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"ApiBaseUrl",
|
|
31
|
+
"AgentConfig",
|
|
32
|
+
"DevicePlatform",
|
|
33
|
+
"AgentProfile",
|
|
34
|
+
"ServerConfig",
|
|
35
|
+
"TaskRequest",
|
|
36
|
+
"TaskStatus",
|
|
37
|
+
"TaskResult",
|
|
38
|
+
"TaskRequestCommon",
|
|
39
|
+
"Task",
|
|
40
|
+
"AgentProfileNotFoundError",
|
|
41
|
+
"AgentTaskRequestError",
|
|
42
|
+
"DeviceNotFoundError",
|
|
43
|
+
"ServerStartupError",
|
|
44
|
+
"AgentError",
|
|
45
|
+
"AgentNotInitializedError",
|
|
46
|
+
"DeviceError",
|
|
47
|
+
"MobileUseError",
|
|
48
|
+
"ServerError",
|
|
49
|
+
]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from typing import Dict, Literal, Optional
|
|
2
|
+
from urllib.parse import urlparse
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from minitap.mobile_use.context import DevicePlatform
|
|
6
|
+
from minitap.mobile_use.sdk.types.task import AgentProfile, TaskRequestCommon
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ApiBaseUrl(BaseModel):
|
|
10
|
+
"""
|
|
11
|
+
Defines an API base URL.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
scheme: Literal["http", "https"]
|
|
15
|
+
host: str
|
|
16
|
+
port: Optional[int] = None
|
|
17
|
+
|
|
18
|
+
def __eq__(self, other):
|
|
19
|
+
if not isinstance(other, ApiBaseUrl):
|
|
20
|
+
return False
|
|
21
|
+
return self.to_url() == other.to_url()
|
|
22
|
+
|
|
23
|
+
def to_url(self):
|
|
24
|
+
return (
|
|
25
|
+
f"{self.scheme}://{self.host}:{self.port}"
|
|
26
|
+
if self.port is not None
|
|
27
|
+
else f"{self.scheme}://{self.host}"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_url(cls, url: str) -> "ApiBaseUrl":
|
|
32
|
+
parsed_url = urlparse(url)
|
|
33
|
+
if parsed_url.scheme not in ["http", "https"]:
|
|
34
|
+
raise ValueError(f"Invalid scheme: {parsed_url.scheme}")
|
|
35
|
+
if parsed_url.hostname is None:
|
|
36
|
+
raise ValueError("Invalid hostname")
|
|
37
|
+
return cls(
|
|
38
|
+
scheme=parsed_url.scheme, # type: ignore
|
|
39
|
+
host=parsed_url.hostname,
|
|
40
|
+
port=parsed_url.port,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ServerConfig(BaseModel):
|
|
45
|
+
"""
|
|
46
|
+
Configuration for the required servers.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
hw_bridge_base_url: ApiBaseUrl
|
|
50
|
+
screen_api_base_url: ApiBaseUrl
|
|
51
|
+
adb_host: str
|
|
52
|
+
adb_port: int
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class AgentConfig(BaseModel):
|
|
56
|
+
"""
|
|
57
|
+
Mobile-use agent configuration.
|
|
58
|
+
|
|
59
|
+
Attributes:
|
|
60
|
+
agent_profiles: Map an agent profile name to its configuration.
|
|
61
|
+
task_config_defaults: Default task request configuration.
|
|
62
|
+
default_profile: default profile to use for tasks
|
|
63
|
+
device_id: Specific device to target (if None, first available is used).
|
|
64
|
+
device_platform: Platform of the device to target.
|
|
65
|
+
servers: Custom server configurations.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
agent_profiles: Dict[str, AgentProfile]
|
|
69
|
+
task_request_defaults: TaskRequestCommon
|
|
70
|
+
default_profile: AgentProfile
|
|
71
|
+
device_id: Optional[str] = None
|
|
72
|
+
device_platform: Optional[DevicePlatform] = None
|
|
73
|
+
servers: ServerConfig
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Exceptions for the Mobile-use SDK.
|
|
3
|
+
|
|
4
|
+
This module defines the exception hierarchy used throughout the Mobile-use SDK.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MobileUseError(Exception):
|
|
9
|
+
"""Base exception class for all Mobile-use SDK exceptions."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, message="An error occurred in the Mobile-use SDK"):
|
|
12
|
+
self.message = message
|
|
13
|
+
super().__init__(self.message)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DeviceError(MobileUseError):
|
|
17
|
+
"""Exception raised for errors related to mobile devices."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, message="A device-related error occurred"):
|
|
20
|
+
super().__init__(message)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DeviceNotFoundError(DeviceError):
|
|
24
|
+
"""Exception raised when no mobile device is found."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, message="No mobile device found"):
|
|
27
|
+
super().__init__(message)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ServerError(MobileUseError):
|
|
31
|
+
"""Exception raised for errors related to Mobile-use servers."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, message="A server-related error occurred"):
|
|
34
|
+
super().__init__(message)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ServerStartupError(ServerError):
|
|
38
|
+
"""Exception raised when Mobile-use servers fail to start."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, server_name=None, message=None):
|
|
41
|
+
if server_name and not message:
|
|
42
|
+
message = f"Failed to start {server_name}"
|
|
43
|
+
elif not message:
|
|
44
|
+
message = "Failed to start Mobile-use servers"
|
|
45
|
+
super().__init__(message)
|
|
46
|
+
self.server_name = server_name
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AgentError(MobileUseError):
|
|
50
|
+
"""Exception raised for errors related to the Mobile-use agent."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, message="An agent-related error occurred"):
|
|
53
|
+
super().__init__(message)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class AgentNotInitializedError(AgentError):
|
|
57
|
+
"""Exception raised when attempting operations on an uninitialized agent."""
|
|
58
|
+
|
|
59
|
+
def __init__(self, message="Agent is not initialized. Call init() first"):
|
|
60
|
+
super().__init__(message)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class AgentTaskRequestError(AgentError):
|
|
64
|
+
"""Exception raised when a requested task is invalid."""
|
|
65
|
+
|
|
66
|
+
def __init__(self, message="An agent task-related error occurred"):
|
|
67
|
+
super().__init__(message)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class AgentProfileNotFoundError(AgentTaskRequestError):
|
|
71
|
+
"""Exception raised when an agent profile is not found."""
|
|
72
|
+
|
|
73
|
+
def __init__(self, profile_name: str):
|
|
74
|
+
super().__init__(f"Agent profile {profile_name} not found")
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task-related type definitions for the Mobile-use SDK.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Generic, Optional, Type, TypeVar, overload
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
from minitap.mobile_use.config import LLMConfig, get_default_llm_config
|
|
12
|
+
from minitap.mobile_use.constants import RECURSION_LIMIT
|
|
13
|
+
from minitap.mobile_use.context import DeviceContext
|
|
14
|
+
from minitap.mobile_use.sdk.utils import load_llm_config_override
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AgentProfile(BaseModel):
|
|
18
|
+
"""
|
|
19
|
+
Represents a mobile-use agent profile.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
name: Name of the agent - used to reference the agent when running tasks.
|
|
23
|
+
llm_config: LLM configuration for the agent.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
name: str
|
|
27
|
+
llm_config: LLMConfig = Field(default_factory=get_default_llm_config)
|
|
28
|
+
|
|
29
|
+
@overload
|
|
30
|
+
def __init__(self, *, name: str, llm_config: LLMConfig): ...
|
|
31
|
+
|
|
32
|
+
@overload
|
|
33
|
+
def __init__(self, *, name: str, from_file: str): ...
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
*,
|
|
38
|
+
name: str,
|
|
39
|
+
llm_config: Optional[LLMConfig] = None,
|
|
40
|
+
from_file: Optional[str] = None,
|
|
41
|
+
**kwargs,
|
|
42
|
+
):
|
|
43
|
+
kwargs["name"] = name
|
|
44
|
+
if from_file:
|
|
45
|
+
kwargs["llm_config"] = load_llm_config_override(Path(from_file))
|
|
46
|
+
elif llm_config:
|
|
47
|
+
kwargs["llm_config"] = llm_config
|
|
48
|
+
else:
|
|
49
|
+
raise ValueError("Either llm_config or from_file must be provided")
|
|
50
|
+
super().__init__(**kwargs)
|
|
51
|
+
|
|
52
|
+
def __str__(self):
|
|
53
|
+
return f"Profile {self.name}:\n{self.llm_config}"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class TaskStatus(str, Enum):
|
|
57
|
+
"""Task execution status enumeration."""
|
|
58
|
+
|
|
59
|
+
PENDING = "PENDING"
|
|
60
|
+
RUNNING = "RUNNING"
|
|
61
|
+
COMPLETED = "COMPLETED"
|
|
62
|
+
FAILED = "FAILED"
|
|
63
|
+
CANCELLED = "CANCELLED"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
T = TypeVar("T", bound=BaseModel)
|
|
67
|
+
TOutput = TypeVar("TOutput", bound=Optional[BaseModel])
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TaskRequestCommon(BaseModel):
|
|
71
|
+
"""
|
|
72
|
+
Defines common parameters of a mobile automation task request.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
max_steps: int = RECURSION_LIMIT
|
|
76
|
+
record_trace: bool = False
|
|
77
|
+
trace_path: Path = Path("mobile-use-traces")
|
|
78
|
+
llm_output_path: Optional[Path] = None
|
|
79
|
+
thoughts_output_path: Optional[Path] = None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TaskRequest(TaskRequestCommon, Generic[TOutput]):
|
|
83
|
+
"""
|
|
84
|
+
Defines the format of a mobile automation task request.
|
|
85
|
+
|
|
86
|
+
Attributes:
|
|
87
|
+
goal: Natural language description of the goal to achieve
|
|
88
|
+
profile: Optional agent profile to use for executing the task
|
|
89
|
+
task_name: Optional name for the task
|
|
90
|
+
output_description: Optional natural language description of expected output format
|
|
91
|
+
output_format: Optional pydantic model for the output format of the task
|
|
92
|
+
max_steps: Maximum number of steps the agent can take (default: 20)
|
|
93
|
+
record_trace: Whether to record a trace (screenshots, actions) of the execution
|
|
94
|
+
(default: False)
|
|
95
|
+
trace_path: Directory path to save trace data if recording is enabled
|
|
96
|
+
llm_output_path: Path to save LLM output data
|
|
97
|
+
thoughts_output_path: Path to save thoughts output data
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
goal: str
|
|
101
|
+
profile: Optional[str] = None
|
|
102
|
+
task_name: Optional[str] = None
|
|
103
|
+
output_description: Optional[str] = None
|
|
104
|
+
output_format: Optional[type[TOutput]] = None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class TaskResult(BaseModel):
|
|
108
|
+
"""
|
|
109
|
+
Result of a mobile automation task.
|
|
110
|
+
|
|
111
|
+
Attributes:
|
|
112
|
+
content: Raw result content (could be text or structured data)
|
|
113
|
+
error: Error message if the task failed
|
|
114
|
+
execution_time_seconds: How long the task took to execute
|
|
115
|
+
steps_taken: Number of steps the agent took to complete the task
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
content: Any = None
|
|
119
|
+
error: Optional[str] = None
|
|
120
|
+
execution_time_seconds: float
|
|
121
|
+
steps_taken: int
|
|
122
|
+
|
|
123
|
+
def get_as_model(self, model_class: Type[T]) -> T:
|
|
124
|
+
"""
|
|
125
|
+
Parse the content into a Pydantic model instance.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
model_class: The Pydantic model class to parse the data into
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
An instance of the specified model class
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
ValueError: If content is None or not compatible with the model
|
|
135
|
+
"""
|
|
136
|
+
if self.content is None:
|
|
137
|
+
raise ValueError("No content available to parse into a model")
|
|
138
|
+
if isinstance(self.content, model_class):
|
|
139
|
+
return self.content
|
|
140
|
+
return model_class.model_validate(self.content)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class Task(BaseModel):
|
|
144
|
+
"""
|
|
145
|
+
A mobile automation task to be executed.
|
|
146
|
+
|
|
147
|
+
Attributes:
|
|
148
|
+
id: Unique identifier for the task
|
|
149
|
+
device: Information about the target device
|
|
150
|
+
status: Current status of the task execution
|
|
151
|
+
request: User task request
|
|
152
|
+
created_at: ISO timestamp when the task was created
|
|
153
|
+
ended_at: ISO timestamp when the task ended
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
id: str
|
|
157
|
+
device: DeviceContext
|
|
158
|
+
status: TaskStatus
|
|
159
|
+
request: TaskRequest
|
|
160
|
+
created_at: datetime
|
|
161
|
+
ended_at: Optional[datetime] = None
|
|
162
|
+
result: Optional[TaskResult] = None
|
|
163
|
+
|
|
164
|
+
def finalize(
|
|
165
|
+
self,
|
|
166
|
+
content: Optional[Any] = None,
|
|
167
|
+
state: Optional[dict] = None,
|
|
168
|
+
error: Optional[str] = None,
|
|
169
|
+
cancelled: bool = False,
|
|
170
|
+
):
|
|
171
|
+
self.status = TaskStatus.COMPLETED if error is None else TaskStatus.FAILED
|
|
172
|
+
if self.status == TaskStatus.FAILED and cancelled:
|
|
173
|
+
self.status = TaskStatus.CANCELLED
|
|
174
|
+
self.ended_at = datetime.now()
|
|
175
|
+
|
|
176
|
+
duration = self.ended_at - self.created_at
|
|
177
|
+
steps_taken = -1
|
|
178
|
+
if state is not None:
|
|
179
|
+
metadata = state.get("metadata", None)
|
|
180
|
+
if metadata:
|
|
181
|
+
steps_taken = metadata.get("step_count", -1)
|
|
182
|
+
|
|
183
|
+
self.result = TaskResult(
|
|
184
|
+
content=content,
|
|
185
|
+
error=error,
|
|
186
|
+
execution_time_seconds=duration.total_seconds(),
|
|
187
|
+
steps_taken=steps_taken,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
def get_name(self) -> str:
|
|
191
|
+
return self.request.task_name or self.id
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from pydantic import ValidationError
|
|
5
|
+
from minitap.mobile_use.config import LLMConfig, deep_merge_llm_config, get_default_llm_config
|
|
6
|
+
from minitap.mobile_use.utils.file import load_jsonc
|
|
7
|
+
from minitap.mobile_use.utils.logger import get_logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_llm_config_override(path: Path) -> LLMConfig:
|
|
14
|
+
default_config = get_default_llm_config()
|
|
15
|
+
|
|
16
|
+
override_config_dict = {}
|
|
17
|
+
if os.path.exists(path):
|
|
18
|
+
logger.info(f"Loading custom LLM config from {path.resolve()}...")
|
|
19
|
+
with open(path, "r") as f:
|
|
20
|
+
override_config_dict = load_jsonc(f)
|
|
21
|
+
else:
|
|
22
|
+
logger.warning("Custom LLM config not found - using the default config")
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
return deep_merge_llm_config(default_config, override_config_dict)
|
|
26
|
+
except ValidationError as e:
|
|
27
|
+
logger.error(f"Invalid LLM config: {e}. Falling back to default config")
|
|
28
|
+
return default_config
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from dotenv import load_dotenv
|
|
3
|
+
from minitap.mobile_use.servers.device_hardware_bridge import DEVICE_HARDWARE_BRIDGE_PORT
|
|
4
|
+
from minitap.mobile_use.utils.logger import get_logger
|
|
5
|
+
from pydantic_settings import BaseSettings
|
|
6
|
+
|
|
7
|
+
load_dotenv(verbose=True)
|
|
8
|
+
logger = get_logger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ServerSettings(BaseSettings):
|
|
12
|
+
DEVICE_HARDWARE_BRIDGE_BASE_URL: str = f"http://localhost:{DEVICE_HARDWARE_BRIDGE_PORT}"
|
|
13
|
+
DEVICE_SCREEN_API_PORT: int = 9998
|
|
14
|
+
ADB_HOST: Optional[str] = None
|
|
15
|
+
|
|
16
|
+
model_config = {"env_file": ".env", "extra": "ignore"}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
server_settings = ServerSettings()
|