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.
- minitap/mobile_use/__init__.py +0 -0
- minitap/mobile_use/agents/contextor/contextor.md +55 -0
- minitap/mobile_use/agents/contextor/contextor.py +175 -0
- minitap/mobile_use/agents/contextor/types.py +36 -0
- minitap/mobile_use/agents/cortex/cortex.md +135 -0
- minitap/mobile_use/agents/cortex/cortex.py +152 -0
- minitap/mobile_use/agents/cortex/types.py +15 -0
- minitap/mobile_use/agents/executor/executor.md +42 -0
- minitap/mobile_use/agents/executor/executor.py +87 -0
- minitap/mobile_use/agents/executor/tool_node.py +152 -0
- minitap/mobile_use/agents/hopper/hopper.md +15 -0
- minitap/mobile_use/agents/hopper/hopper.py +44 -0
- minitap/mobile_use/agents/orchestrator/human.md +12 -0
- minitap/mobile_use/agents/orchestrator/orchestrator.md +21 -0
- minitap/mobile_use/agents/orchestrator/orchestrator.py +134 -0
- minitap/mobile_use/agents/orchestrator/types.py +11 -0
- minitap/mobile_use/agents/outputter/human.md +25 -0
- minitap/mobile_use/agents/outputter/outputter.py +85 -0
- minitap/mobile_use/agents/outputter/test_outputter.py +167 -0
- minitap/mobile_use/agents/planner/human.md +14 -0
- minitap/mobile_use/agents/planner/planner.md +126 -0
- minitap/mobile_use/agents/planner/planner.py +101 -0
- minitap/mobile_use/agents/planner/types.py +51 -0
- minitap/mobile_use/agents/planner/utils.py +70 -0
- minitap/mobile_use/agents/summarizer/summarizer.py +35 -0
- minitap/mobile_use/agents/video_analyzer/__init__.py +5 -0
- minitap/mobile_use/agents/video_analyzer/human.md +5 -0
- minitap/mobile_use/agents/video_analyzer/video_analyzer.md +37 -0
- minitap/mobile_use/agents/video_analyzer/video_analyzer.py +111 -0
- minitap/mobile_use/clients/browserstack_client.py +477 -0
- minitap/mobile_use/clients/idb_client.py +429 -0
- minitap/mobile_use/clients/ios_client.py +332 -0
- minitap/mobile_use/clients/ios_client_config.py +141 -0
- minitap/mobile_use/clients/ui_automator_client.py +330 -0
- minitap/mobile_use/clients/wda_client.py +526 -0
- minitap/mobile_use/clients/wda_lifecycle.py +367 -0
- minitap/mobile_use/config.py +413 -0
- minitap/mobile_use/constants.py +3 -0
- minitap/mobile_use/context.py +106 -0
- minitap/mobile_use/controllers/__init__.py +0 -0
- minitap/mobile_use/controllers/android_controller.py +524 -0
- minitap/mobile_use/controllers/controller_factory.py +46 -0
- minitap/mobile_use/controllers/device_controller.py +182 -0
- minitap/mobile_use/controllers/ios_controller.py +436 -0
- minitap/mobile_use/controllers/platform_specific_commands_controller.py +199 -0
- minitap/mobile_use/controllers/types.py +106 -0
- minitap/mobile_use/controllers/unified_controller.py +193 -0
- minitap/mobile_use/graph/graph.py +160 -0
- minitap/mobile_use/graph/state.py +115 -0
- minitap/mobile_use/main.py +309 -0
- minitap/mobile_use/sdk/__init__.py +12 -0
- minitap/mobile_use/sdk/agent.py +1294 -0
- minitap/mobile_use/sdk/builders/__init__.py +10 -0
- minitap/mobile_use/sdk/builders/agent_config_builder.py +307 -0
- minitap/mobile_use/sdk/builders/index.py +15 -0
- minitap/mobile_use/sdk/builders/task_request_builder.py +236 -0
- minitap/mobile_use/sdk/constants.py +1 -0
- minitap/mobile_use/sdk/examples/README.md +83 -0
- minitap/mobile_use/sdk/examples/__init__.py +1 -0
- minitap/mobile_use/sdk/examples/app_lock_messaging.py +54 -0
- minitap/mobile_use/sdk/examples/platform_manual_task_example.py +67 -0
- minitap/mobile_use/sdk/examples/platform_minimal_example.py +48 -0
- minitap/mobile_use/sdk/examples/simple_photo_organizer.py +76 -0
- minitap/mobile_use/sdk/examples/smart_notification_assistant.py +225 -0
- minitap/mobile_use/sdk/examples/video_transcription_example.py +117 -0
- minitap/mobile_use/sdk/services/cloud_mobile.py +656 -0
- minitap/mobile_use/sdk/services/platform.py +434 -0
- minitap/mobile_use/sdk/types/__init__.py +51 -0
- minitap/mobile_use/sdk/types/agent.py +84 -0
- minitap/mobile_use/sdk/types/exceptions.py +138 -0
- minitap/mobile_use/sdk/types/platform.py +183 -0
- minitap/mobile_use/sdk/types/task.py +269 -0
- minitap/mobile_use/sdk/utils.py +29 -0
- minitap/mobile_use/services/accessibility.py +100 -0
- minitap/mobile_use/services/llm.py +247 -0
- minitap/mobile_use/services/telemetry.py +421 -0
- minitap/mobile_use/tools/index.py +67 -0
- minitap/mobile_use/tools/mobile/back.py +52 -0
- minitap/mobile_use/tools/mobile/erase_one_char.py +56 -0
- minitap/mobile_use/tools/mobile/focus_and_clear_text.py +317 -0
- minitap/mobile_use/tools/mobile/focus_and_input_text.py +153 -0
- minitap/mobile_use/tools/mobile/launch_app.py +86 -0
- minitap/mobile_use/tools/mobile/long_press_on.py +169 -0
- minitap/mobile_use/tools/mobile/open_link.py +62 -0
- minitap/mobile_use/tools/mobile/press_key.py +83 -0
- minitap/mobile_use/tools/mobile/stop_app.py +62 -0
- minitap/mobile_use/tools/mobile/swipe.py +156 -0
- minitap/mobile_use/tools/mobile/tap.py +154 -0
- minitap/mobile_use/tools/mobile/video_recording.py +177 -0
- minitap/mobile_use/tools/mobile/wait_for_delay.py +81 -0
- minitap/mobile_use/tools/scratchpad.py +147 -0
- minitap/mobile_use/tools/test_utils.py +413 -0
- minitap/mobile_use/tools/tool_wrapper.py +16 -0
- minitap/mobile_use/tools/types.py +35 -0
- minitap/mobile_use/tools/utils.py +336 -0
- minitap/mobile_use/utils/app_launch_utils.py +173 -0
- minitap/mobile_use/utils/cli_helpers.py +37 -0
- minitap/mobile_use/utils/cli_selection.py +143 -0
- minitap/mobile_use/utils/conversations.py +31 -0
- minitap/mobile_use/utils/decorators.py +124 -0
- minitap/mobile_use/utils/errors.py +6 -0
- minitap/mobile_use/utils/file.py +13 -0
- minitap/mobile_use/utils/logger.py +183 -0
- minitap/mobile_use/utils/media.py +186 -0
- minitap/mobile_use/utils/recorder.py +52 -0
- minitap/mobile_use/utils/requests_utils.py +37 -0
- minitap/mobile_use/utils/shell_utils.py +20 -0
- minitap/mobile_use/utils/test_ui_hierarchy.py +178 -0
- minitap/mobile_use/utils/time.py +6 -0
- minitap/mobile_use/utils/ui_hierarchy.py +132 -0
- minitap/mobile_use/utils/video.py +281 -0
- minitap_mobile_use-3.3.0.dist-info/METADATA +329 -0
- minitap_mobile_use-3.3.0.dist-info/RECORD +115 -0
- minitap_mobile_use-3.3.0.dist-info/WHEEL +4 -0
- minitap_mobile_use-3.3.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,138 @@
|
|
|
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
|
+
from typing import Literal
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MobileUseError(Exception):
|
|
11
|
+
"""Base exception class for all Mobile-use SDK exceptions."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, message="An error occurred in the Mobile-use SDK"):
|
|
14
|
+
self.message = message
|
|
15
|
+
super().__init__(self.message)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DeviceError(MobileUseError):
|
|
19
|
+
"""Exception raised for errors related to mobile devices."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, message="A device-related error occurred"):
|
|
22
|
+
super().__init__(message)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DeviceNotFoundError(DeviceError):
|
|
26
|
+
"""Exception raised when no mobile device is found."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, message="No mobile device found"):
|
|
29
|
+
super().__init__(message)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ServerError(MobileUseError):
|
|
33
|
+
"""Exception raised for errors related to Mobile-use servers."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, message="A server-related error occurred"):
|
|
36
|
+
super().__init__(message)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ServerStartupError(ServerError):
|
|
40
|
+
"""Exception raised when Mobile-use servers fail to start."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, server_name=None, message=None):
|
|
43
|
+
if server_name and not message:
|
|
44
|
+
message = f"Failed to start {server_name}"
|
|
45
|
+
elif not message:
|
|
46
|
+
message = "Failed to start Mobile-use servers"
|
|
47
|
+
super().__init__(message)
|
|
48
|
+
self.server_name = server_name
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AgentError(MobileUseError):
|
|
52
|
+
"""Exception raised for errors related to the Mobile-use agent."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, message="An agent-related error occurred"):
|
|
55
|
+
super().__init__(message)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class AgentNotInitializedError(AgentError):
|
|
59
|
+
"""Exception raised when attempting operations on an uninitialized agent."""
|
|
60
|
+
|
|
61
|
+
def __init__(self, message="Agent is not initialized. Call init() first"):
|
|
62
|
+
super().__init__(message)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class AgentTaskRequestError(AgentError):
|
|
66
|
+
"""Exception raised when a requested task is invalid."""
|
|
67
|
+
|
|
68
|
+
def __init__(self, message="An agent task-related error occurred"):
|
|
69
|
+
super().__init__(message)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class AgentProfileNotFoundError(AgentTaskRequestError):
|
|
73
|
+
"""Exception raised when an agent profile is not found."""
|
|
74
|
+
|
|
75
|
+
def __init__(self, profile_name: str):
|
|
76
|
+
super().__init__(f"Agent profile {profile_name} not found")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
EXECUTABLES = Literal["adb", "xcrun", "idb", "cli_tools"]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class ExecutableNotFoundError(MobileUseError):
|
|
83
|
+
"""Exception raised when a required executable is not found."""
|
|
84
|
+
|
|
85
|
+
def __init__(self, executable_name: EXECUTABLES):
|
|
86
|
+
install_instructions: dict[EXECUTABLES, str] = {
|
|
87
|
+
"adb": "https://developer.android.com/tools/adb",
|
|
88
|
+
"idb": "https://fbidb.io/docs/installation/",
|
|
89
|
+
"xcrun": "Install with: xcode-select --install",
|
|
90
|
+
}
|
|
91
|
+
if executable_name == "cli_tools":
|
|
92
|
+
message = (
|
|
93
|
+
"ADB or Xcode Command Line Tools not found in PATH. "
|
|
94
|
+
"At least one of them is required to run mobile-use "
|
|
95
|
+
"depending on the device platform you wish to run (Android: adb, iOS: xcrun)."
|
|
96
|
+
"Refer to the following links for installation instructions :"
|
|
97
|
+
f"\n- ADB: {install_instructions['adb']}"
|
|
98
|
+
f"\n- Xcode Command Line Tools: {install_instructions['xcrun']}"
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
message = f"Required executable '{executable_name}' not found in PATH."
|
|
102
|
+
if executable_name in install_instructions:
|
|
103
|
+
message += f"\nTo install it, please visit: {install_instructions[executable_name]}"
|
|
104
|
+
super().__init__(message)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class AgentInvalidApiKeyError(AgentTaskRequestError):
|
|
108
|
+
"""Exception raise when the API key could not have been found"""
|
|
109
|
+
|
|
110
|
+
def __init__(self):
|
|
111
|
+
super().__init__(
|
|
112
|
+
"Minitap API key is incorrect. Visit https://platform.minitap.ai/api-keys "
|
|
113
|
+
"to get your API key."
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class PlatformServiceUninitializedError(MobileUseError):
|
|
118
|
+
"""Exception raised when a platform service call fails."""
|
|
119
|
+
|
|
120
|
+
def __init__(self):
|
|
121
|
+
super().__init__(
|
|
122
|
+
"Platform service is not initialized. "
|
|
123
|
+
"To use Minitap platform service, visit https://platform.minitap.ai.",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class CloudMobileServiceUninitializedError(MobileUseError):
|
|
128
|
+
"""Exception raised when a cloud mobile service call fails."""
|
|
129
|
+
|
|
130
|
+
def __init__(self):
|
|
131
|
+
super().__init__("Cloud mobile service is not initialized!")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class PlatformServiceError(MobileUseError):
|
|
135
|
+
"""Exception raised when a platform service call fails."""
|
|
136
|
+
|
|
137
|
+
def __init__(self, message="A platform service-related error occurred"):
|
|
138
|
+
super().__init__(message)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Annotated, Any, Literal
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field, PlainSerializer
|
|
5
|
+
from pydantic.v1.utils import to_lower_camel
|
|
6
|
+
|
|
7
|
+
TaskRunStatus = Literal["pending", "running", "completed", "failed", "cancelled"]
|
|
8
|
+
|
|
9
|
+
IsoDatetime = Annotated[
|
|
10
|
+
datetime,
|
|
11
|
+
PlainSerializer(
|
|
12
|
+
func=lambda v: v.isoformat() if v else None,
|
|
13
|
+
return_type=str,
|
|
14
|
+
when_used="unless-none",
|
|
15
|
+
),
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BaseApiModel(BaseModel):
|
|
20
|
+
model_config = ConfigDict(
|
|
21
|
+
alias_generator=to_lower_camel,
|
|
22
|
+
populate_by_name=True,
|
|
23
|
+
str_strip_whitespace=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LLMProfileResponse(BaseApiModel):
|
|
28
|
+
"""Response model for LLM profile."""
|
|
29
|
+
|
|
30
|
+
id: str = Field(..., description="Profile ID")
|
|
31
|
+
name: str = Field(..., description="Profile name")
|
|
32
|
+
description: str | None = Field(None, description="Profile description")
|
|
33
|
+
llms: dict[str, Any] = Field(..., description="LLM configuration")
|
|
34
|
+
created_at: str = Field(..., description="Creation timestamp (ISO format)")
|
|
35
|
+
updated_at: str = Field(..., description="Last update timestamp (ISO format)")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TaskOptionsResponse(BaseApiModel):
|
|
39
|
+
"""Response model for task options."""
|
|
40
|
+
|
|
41
|
+
id: str = Field(..., description="Options ID")
|
|
42
|
+
enable_tracing: bool = Field(..., description="Whether tracing is enabled")
|
|
43
|
+
max_steps: int = Field(..., description="Maximum number of steps")
|
|
44
|
+
enable_video_tools: bool = Field(..., description="Whether video tools are enabled")
|
|
45
|
+
created_at: str = Field(..., description="Creation timestamp (ISO format)")
|
|
46
|
+
updated_at: str = Field(..., description="Last update timestamp (ISO format)")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TaskResponse(BaseApiModel):
|
|
50
|
+
"""Response model for task."""
|
|
51
|
+
|
|
52
|
+
id: str = Field(..., description="Task ID")
|
|
53
|
+
name: str = Field(..., description="Task name")
|
|
54
|
+
description: str | None = Field(None, description="Task description")
|
|
55
|
+
input_prompt: str = Field(..., description="Input prompt")
|
|
56
|
+
output_description: str | None = Field(None, description="Output description")
|
|
57
|
+
options: TaskOptionsResponse = Field(..., description="Task options")
|
|
58
|
+
created_at: str = Field(..., description="Creation timestamp (ISO format)")
|
|
59
|
+
updated_at: str = Field(..., description="Last update timestamp (ISO format)")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class CreateTaskRunRequest(BaseApiModel):
|
|
63
|
+
"""Request model for creating a task run."""
|
|
64
|
+
|
|
65
|
+
task_id: str = Field(..., description="ID of the task to run")
|
|
66
|
+
llm_profile_id: str = Field(..., description="LLM profile ID to use")
|
|
67
|
+
virtual_mobile_id: str | None = Field(None, description="Virtual mobile ID to use")
|
|
68
|
+
locked_app_package: str | None = Field(None, description="App package to lock for the task run")
|
|
69
|
+
execution_origin: str | None = Field(None, description="Origin of the task execution")
|
|
70
|
+
enable_video_tools: bool = Field(
|
|
71
|
+
default=False,
|
|
72
|
+
description="Whether video tools are enabled for this task run",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class CreateOrphanTaskRunRequest(BaseApiModel):
|
|
77
|
+
"""Request model for creating an orphan task run (without a parent task)."""
|
|
78
|
+
|
|
79
|
+
input_prompt: str = Field(
|
|
80
|
+
...,
|
|
81
|
+
description="Input prompt for the task run",
|
|
82
|
+
)
|
|
83
|
+
output_description: str | None = Field(
|
|
84
|
+
None,
|
|
85
|
+
description="Optional output description",
|
|
86
|
+
)
|
|
87
|
+
llm_profile_id: str = Field(
|
|
88
|
+
...,
|
|
89
|
+
description="LLM profile ID to use",
|
|
90
|
+
)
|
|
91
|
+
virtual_mobile_id: str | None = Field(
|
|
92
|
+
None,
|
|
93
|
+
description="Optional virtual mobile ID on which it runs",
|
|
94
|
+
)
|
|
95
|
+
locked_app_package: str | None = Field(
|
|
96
|
+
None,
|
|
97
|
+
description="Optional app package to lock execution to",
|
|
98
|
+
)
|
|
99
|
+
execution_origin: str | None = Field(
|
|
100
|
+
None,
|
|
101
|
+
description="Origin of the task run execution",
|
|
102
|
+
)
|
|
103
|
+
max_steps: int | None = Field(
|
|
104
|
+
None,
|
|
105
|
+
description="Maximum number of steps for the task run",
|
|
106
|
+
)
|
|
107
|
+
enable_video_tools: bool = Field(
|
|
108
|
+
default=False,
|
|
109
|
+
description="Whether video tools are enabled for this task run",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class UpdateTaskRunStatusRequest(BaseApiModel):
|
|
114
|
+
"""Request model for updating task run status."""
|
|
115
|
+
|
|
116
|
+
status: TaskRunStatus = Field(..., description="New status of the task run")
|
|
117
|
+
message: str | None = Field(None, description="Message associated with the status")
|
|
118
|
+
output: str | None = Field(None, description="Output of the task run")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TaskRunResponse(BaseApiModel):
|
|
122
|
+
"""Response model for a single task run."""
|
|
123
|
+
|
|
124
|
+
id: str = Field(..., description="Unique identifier for the task run")
|
|
125
|
+
task: TaskResponse | None = Field(
|
|
126
|
+
..., description="ID of the task this run is for or None if manually created"
|
|
127
|
+
)
|
|
128
|
+
llm_profile: LLMProfileResponse = Field(..., description="LLM profile ID used for this run")
|
|
129
|
+
status: TaskRunStatus = Field(..., description="Current status of the task run")
|
|
130
|
+
input_prompt: str = Field(..., description="Input prompt for this task run")
|
|
131
|
+
output_description: str | None = Field(None, description="Description of expected output")
|
|
132
|
+
created_at: datetime = Field(..., description="When the task run was created")
|
|
133
|
+
started_at: datetime | None = Field(None, description="When the task run started")
|
|
134
|
+
finished_at: datetime | None = Field(None, description="When the task run finished")
|
|
135
|
+
locked_app_package: str | None = Field(
|
|
136
|
+
None, description="The app package to lock for the task run"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
SubgoalState = Literal["pending", "started", "completed", "failed"]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class MobileUseSubgoal(BaseModel):
|
|
144
|
+
"""Upsert MobileUseSubgoal API model."""
|
|
145
|
+
|
|
146
|
+
name: str = Field(..., description="Name of the subgoal")
|
|
147
|
+
state: SubgoalState = Field(default="pending", description="Current state of the subgoal")
|
|
148
|
+
started_at: IsoDatetime | None = Field(default=None, description="When the subgoal started")
|
|
149
|
+
ended_at: IsoDatetime | None = Field(default=None, description="When the subgoal ended")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class UpsertTaskRunPlanRequest(BaseApiModel):
|
|
153
|
+
"""Upsert MobileUseSubgoal API model."""
|
|
154
|
+
|
|
155
|
+
started_at: IsoDatetime = Field(..., description="When the plan started")
|
|
156
|
+
subgoals: list[MobileUseSubgoal] = Field(..., description="Subgoals of the plan")
|
|
157
|
+
ended_at: IsoDatetime | None = Field(
|
|
158
|
+
default=None,
|
|
159
|
+
description="When the plan ended (replanned or completed)",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class TaskRunPlanResponse(UpsertTaskRunPlanRequest):
|
|
164
|
+
"""Response model for a task run plan."""
|
|
165
|
+
|
|
166
|
+
id: str = Field(..., description="Unique identifier for the task run plan")
|
|
167
|
+
task_run_id: str = Field(..., description="ID of the task run this plan is for")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class UpsertTaskRunAgentThoughtRequest(BaseApiModel):
|
|
171
|
+
"""Upsert MobileUseAgentThought request model."""
|
|
172
|
+
|
|
173
|
+
agent: str = Field(..., description="Agent that produced the thought")
|
|
174
|
+
content: str = Field(..., description="Content of the thought")
|
|
175
|
+
timestamp: IsoDatetime = Field(..., description="Timestamp of the thought (UTC)")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class TrajectoryGifUploadResponse(BaseApiModel):
|
|
179
|
+
"""Response containing trajectory GIF signed upload URL."""
|
|
180
|
+
|
|
181
|
+
signed_url: str = Field(..., description="Signed upload URL for the trajectory GIF")
|
|
182
|
+
task_run_id: str = Field(..., description="Task run ID for which the GIF URL was generated")
|
|
183
|
+
file_path: str = Field(..., description="Path where the GIF will be stored")
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task-related type definitions for the Mobile-use SDK.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import tempfile
|
|
6
|
+
from asyncio import Event
|
|
7
|
+
from collections.abc import Callable, Coroutine
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, TypeVar, overload
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
13
|
+
|
|
14
|
+
from minitap.mobile_use.config import LLMConfig, get_default_llm_config
|
|
15
|
+
from minitap.mobile_use.constants import RECURSION_LIMIT
|
|
16
|
+
from minitap.mobile_use.context import DeviceContext
|
|
17
|
+
from minitap.mobile_use.sdk.types.platform import TaskRunResponse, TaskRunStatus
|
|
18
|
+
from minitap.mobile_use.sdk.utils import load_llm_config_override
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AgentProfile(BaseModel):
|
|
22
|
+
"""
|
|
23
|
+
Represents a mobile-use agent profile.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
name: Name of the agent - used to reference the agent when running tasks.
|
|
27
|
+
llm_config: LLM configuration for the agent.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
name: str
|
|
31
|
+
llm_config: LLMConfig = Field(default_factory=get_default_llm_config)
|
|
32
|
+
|
|
33
|
+
@overload
|
|
34
|
+
def __init__(self, *, name: str, llm_config: LLMConfig): ...
|
|
35
|
+
|
|
36
|
+
@overload
|
|
37
|
+
def __init__(self, *, name: str, from_file: str): ...
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
*,
|
|
42
|
+
name: str,
|
|
43
|
+
llm_config: LLMConfig | None = None,
|
|
44
|
+
from_file: str | None = None,
|
|
45
|
+
**kwargs,
|
|
46
|
+
):
|
|
47
|
+
kwargs["name"] = name
|
|
48
|
+
if from_file:
|
|
49
|
+
kwargs["llm_config"] = load_llm_config_override(Path(from_file))
|
|
50
|
+
elif llm_config:
|
|
51
|
+
kwargs["llm_config"] = llm_config
|
|
52
|
+
else:
|
|
53
|
+
raise ValueError("Either llm_config or from_file must be provided")
|
|
54
|
+
super().__init__(**kwargs)
|
|
55
|
+
|
|
56
|
+
def __str__(self):
|
|
57
|
+
return f"Profile {self.name}:\n{self.llm_config}"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
T = TypeVar("T", bound=BaseModel)
|
|
61
|
+
TOutput = TypeVar("TOutput", bound=BaseModel | None)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TaskRequestBase(BaseModel):
|
|
65
|
+
"""
|
|
66
|
+
Defines common parameters of a mobile automation task request.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
max_steps: int = RECURSION_LIMIT
|
|
70
|
+
record_trace: bool = False
|
|
71
|
+
trace_path: Path = Path("mobile-use-traces")
|
|
72
|
+
llm_output_path: Path | None = None
|
|
73
|
+
thoughts_output_path: Path | None = None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class TaskRequestCommon(TaskRequestBase):
|
|
77
|
+
"""
|
|
78
|
+
Defines common parameters for any task request.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
max_steps: int = RECURSION_LIMIT
|
|
82
|
+
locked_app_package: str | None = None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TaskRequest[TOutput](TaskRequestCommon):
|
|
86
|
+
"""
|
|
87
|
+
Defines the format of a mobile automation task request.
|
|
88
|
+
|
|
89
|
+
Attributes:
|
|
90
|
+
goal: Natural language description of the goal to achieve
|
|
91
|
+
profile: Optional agent profile to use for executing the task
|
|
92
|
+
task_name: Optional name for the task
|
|
93
|
+
output_description: Optional natural language description of expected output format
|
|
94
|
+
output_format: Optional pydantic model for the output format of the task
|
|
95
|
+
max_steps: Maximum number of steps the agent can take (default: 20)
|
|
96
|
+
record_trace: Whether to record a trace (screenshots, actions) of the execution
|
|
97
|
+
(default: False)
|
|
98
|
+
trace_path: Directory path to save trace data if recording is enabled
|
|
99
|
+
llm_output_path: Path to save LLM output data
|
|
100
|
+
thoughts_output_path: Path to save thoughts output data
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
goal: str
|
|
104
|
+
profile: str | None = None
|
|
105
|
+
task_name: str | None = None
|
|
106
|
+
output_description: str | None = None
|
|
107
|
+
output_format: type[TOutput] | None = None
|
|
108
|
+
enable_remote_tracing: bool = False
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ManualTaskConfig(BaseModel):
|
|
112
|
+
"""
|
|
113
|
+
Configuration for manually creating a task without fetching from the platform.
|
|
114
|
+
|
|
115
|
+
Attributes:
|
|
116
|
+
goal: Natural language description of the goal to achieve
|
|
117
|
+
output_description: Optional natural language description of expected output format
|
|
118
|
+
task_name: Optional name for the task
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
goal: str
|
|
122
|
+
output_description: str | None = None
|
|
123
|
+
task_name: str | None = None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class PlatformTaskRequest[TOutput](TaskRequestBase):
|
|
127
|
+
"""
|
|
128
|
+
Minitap-specific task request for SDK usage via the gateway platform.
|
|
129
|
+
|
|
130
|
+
Attributes:
|
|
131
|
+
task: Either a task name to fetch from the platform, or a
|
|
132
|
+
ManualTaskConfig to create manually
|
|
133
|
+
profile: Optional profile name specified by the user on the platform
|
|
134
|
+
execution_origin: Origin of the task execution (default: "sdk")
|
|
135
|
+
record_trace: Whether to record traces (default: True for platform tasks)
|
|
136
|
+
trace_path: Path to save traces (default: temp directory)
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
task: str | ManualTaskConfig
|
|
140
|
+
profile: str | None = None
|
|
141
|
+
execution_origin: str = "sdk"
|
|
142
|
+
record_trace: bool = True
|
|
143
|
+
trace_path: Path = Path(tempfile.gettempdir()) / "mobile-use-traces"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class CloudDevicePlatformTaskRequest[TOutput](PlatformTaskRequest[TOutput]):
|
|
147
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
148
|
+
|
|
149
|
+
task_run_id_available_event: Event = Event()
|
|
150
|
+
task_run_id: str | None = None
|
|
151
|
+
virtual_mobile_id: str | None = None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class TaskResult(BaseModel):
|
|
155
|
+
"""
|
|
156
|
+
Result of a mobile automation task.
|
|
157
|
+
|
|
158
|
+
Attributes:
|
|
159
|
+
content: Raw result content (could be text or structured data)
|
|
160
|
+
error: Error message if the task failed
|
|
161
|
+
execution_time_seconds: How long the task took to execute
|
|
162
|
+
steps_taken: Number of steps the agent took to complete the task
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
content: Any = None
|
|
166
|
+
error: str | None = None
|
|
167
|
+
execution_time_seconds: float
|
|
168
|
+
steps_taken: int
|
|
169
|
+
|
|
170
|
+
def get_as_model(self, model_class: type[T]) -> T:
|
|
171
|
+
"""
|
|
172
|
+
Parse the content into a Pydantic model instance.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
model_class: The Pydantic model class to parse the data into
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
An instance of the specified model class
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
ValueError: If content is None or not compatible with the model
|
|
182
|
+
"""
|
|
183
|
+
if self.content is None:
|
|
184
|
+
raise ValueError("No content available to parse into a model")
|
|
185
|
+
if isinstance(self.content, model_class):
|
|
186
|
+
return self.content
|
|
187
|
+
return model_class.model_validate(self.content)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class Task(BaseModel):
|
|
191
|
+
"""
|
|
192
|
+
A mobile automation task to be executed.
|
|
193
|
+
|
|
194
|
+
Attributes:
|
|
195
|
+
id: Unique identifier for the task
|
|
196
|
+
device: Information about the target device
|
|
197
|
+
status: Current status of the task execution
|
|
198
|
+
request: User task request
|
|
199
|
+
created_at: ISO timestamp when the task was created
|
|
200
|
+
ended_at: ISO timestamp when the task ended
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
id: str
|
|
204
|
+
device: DeviceContext
|
|
205
|
+
status: TaskRunStatus
|
|
206
|
+
status_message: str | None = None
|
|
207
|
+
on_status_changed: Callable[[TaskRunStatus, str | None, Any | None], Coroutine] | None = None
|
|
208
|
+
request: TaskRequest
|
|
209
|
+
created_at: datetime
|
|
210
|
+
ended_at: datetime | None = None
|
|
211
|
+
result: TaskResult | None = None
|
|
212
|
+
|
|
213
|
+
async def finalize(
|
|
214
|
+
self,
|
|
215
|
+
content: Any | None = None,
|
|
216
|
+
state: dict | None = None,
|
|
217
|
+
error: str | None = None,
|
|
218
|
+
cancelled: bool = False,
|
|
219
|
+
):
|
|
220
|
+
new_status: TaskRunStatus = "completed" if error is None else "failed"
|
|
221
|
+
if new_status == "failed" and cancelled:
|
|
222
|
+
new_status = "cancelled"
|
|
223
|
+
message = "Task completed successfully"
|
|
224
|
+
if new_status == "failed":
|
|
225
|
+
message = "Task failed" + (f": {error}" if error else "")
|
|
226
|
+
elif new_status == "cancelled":
|
|
227
|
+
message = "Task cancelled" + (f": {error}" if error else "")
|
|
228
|
+
await self.set_status(status=new_status, message=message, output=content or error)
|
|
229
|
+
self.ended_at = datetime.now()
|
|
230
|
+
|
|
231
|
+
duration = self.ended_at - self.created_at
|
|
232
|
+
steps_taken = -1
|
|
233
|
+
if state is not None:
|
|
234
|
+
metadata = state.get("metadata", None)
|
|
235
|
+
if metadata:
|
|
236
|
+
steps_taken = metadata.get("step_count", -1)
|
|
237
|
+
|
|
238
|
+
self.result = TaskResult(
|
|
239
|
+
content=content,
|
|
240
|
+
error=error,
|
|
241
|
+
execution_time_seconds=duration.total_seconds(),
|
|
242
|
+
steps_taken=steps_taken,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
def get_name(self) -> str:
|
|
246
|
+
if isinstance(self.request, PlatformTaskRequest):
|
|
247
|
+
if isinstance(self.request.task, str):
|
|
248
|
+
return self.request.task
|
|
249
|
+
else:
|
|
250
|
+
# ManualTaskConfig - use first 50 chars of goal
|
|
251
|
+
return f"Manual: {self.request.task.goal[:50]}"
|
|
252
|
+
return self.request.task_name or self.id
|
|
253
|
+
|
|
254
|
+
async def set_status(
|
|
255
|
+
self,
|
|
256
|
+
status: TaskRunStatus,
|
|
257
|
+
message: str | None = None,
|
|
258
|
+
output: Any | None = None,
|
|
259
|
+
):
|
|
260
|
+
self.status = status
|
|
261
|
+
self.status_message = message
|
|
262
|
+
if self.on_status_changed:
|
|
263
|
+
await self.on_status_changed(status, message, output)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class PlatformTaskInfo(BaseModel):
|
|
267
|
+
task_request: TaskRequest = Field(..., description="Task request")
|
|
268
|
+
llm_profile: AgentProfile = Field(..., description="LLM profile")
|
|
269
|
+
task_run: TaskRunResponse = Field(..., description="Task run instance on the platform")
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from pydantic import ValidationError
|
|
5
|
+
|
|
6
|
+
from minitap.mobile_use.config import LLMConfig, deep_merge_llm_config, get_default_llm_config
|
|
7
|
+
from minitap.mobile_use.utils.file import load_jsonc
|
|
8
|
+
from minitap.mobile_use.utils.logger import get_logger
|
|
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) 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}")
|
|
28
|
+
logger.info("Falling back to default config")
|
|
29
|
+
return default_config
|