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.

Files changed (95) hide show
  1. minitap/mobile_use/__init__.py +0 -0
  2. minitap/mobile_use/agents/contextor/contextor.py +42 -0
  3. minitap/mobile_use/agents/cortex/cortex.md +93 -0
  4. minitap/mobile_use/agents/cortex/cortex.py +107 -0
  5. minitap/mobile_use/agents/cortex/types.py +11 -0
  6. minitap/mobile_use/agents/executor/executor.md +73 -0
  7. minitap/mobile_use/agents/executor/executor.py +84 -0
  8. minitap/mobile_use/agents/executor/executor_context_cleaner.py +27 -0
  9. minitap/mobile_use/agents/executor/utils.py +11 -0
  10. minitap/mobile_use/agents/hopper/hopper.md +13 -0
  11. minitap/mobile_use/agents/hopper/hopper.py +45 -0
  12. minitap/mobile_use/agents/orchestrator/human.md +13 -0
  13. minitap/mobile_use/agents/orchestrator/orchestrator.md +18 -0
  14. minitap/mobile_use/agents/orchestrator/orchestrator.py +114 -0
  15. minitap/mobile_use/agents/orchestrator/types.py +14 -0
  16. minitap/mobile_use/agents/outputter/human.md +25 -0
  17. minitap/mobile_use/agents/outputter/outputter.py +75 -0
  18. minitap/mobile_use/agents/outputter/test_outputter.py +107 -0
  19. minitap/mobile_use/agents/planner/human.md +12 -0
  20. minitap/mobile_use/agents/planner/planner.md +64 -0
  21. minitap/mobile_use/agents/planner/planner.py +64 -0
  22. minitap/mobile_use/agents/planner/types.py +44 -0
  23. minitap/mobile_use/agents/planner/utils.py +45 -0
  24. minitap/mobile_use/agents/summarizer/summarizer.py +34 -0
  25. minitap/mobile_use/clients/device_hardware_client.py +23 -0
  26. minitap/mobile_use/clients/ios_client.py +44 -0
  27. minitap/mobile_use/clients/screen_api_client.py +53 -0
  28. minitap/mobile_use/config.py +285 -0
  29. minitap/mobile_use/constants.py +2 -0
  30. minitap/mobile_use/context.py +65 -0
  31. minitap/mobile_use/controllers/__init__.py +0 -0
  32. minitap/mobile_use/controllers/mobile_command_controller.py +379 -0
  33. minitap/mobile_use/controllers/platform_specific_commands_controller.py +74 -0
  34. minitap/mobile_use/graph/graph.py +149 -0
  35. minitap/mobile_use/graph/state.py +73 -0
  36. minitap/mobile_use/main.py +122 -0
  37. minitap/mobile_use/sdk/__init__.py +12 -0
  38. minitap/mobile_use/sdk/agent.py +524 -0
  39. minitap/mobile_use/sdk/builders/__init__.py +10 -0
  40. minitap/mobile_use/sdk/builders/agent_config_builder.py +213 -0
  41. minitap/mobile_use/sdk/builders/index.py +15 -0
  42. minitap/mobile_use/sdk/builders/task_request_builder.py +218 -0
  43. minitap/mobile_use/sdk/constants.py +14 -0
  44. minitap/mobile_use/sdk/examples/README.md +45 -0
  45. minitap/mobile_use/sdk/examples/__init__.py +1 -0
  46. minitap/mobile_use/sdk/examples/simple_photo_organizer.py +76 -0
  47. minitap/mobile_use/sdk/examples/smart_notification_assistant.py +177 -0
  48. minitap/mobile_use/sdk/types/__init__.py +49 -0
  49. minitap/mobile_use/sdk/types/agent.py +73 -0
  50. minitap/mobile_use/sdk/types/exceptions.py +74 -0
  51. minitap/mobile_use/sdk/types/task.py +191 -0
  52. minitap/mobile_use/sdk/utils.py +28 -0
  53. minitap/mobile_use/servers/config.py +19 -0
  54. minitap/mobile_use/servers/device_hardware_bridge.py +212 -0
  55. minitap/mobile_use/servers/device_screen_api.py +143 -0
  56. minitap/mobile_use/servers/start_servers.py +151 -0
  57. minitap/mobile_use/servers/stop_servers.py +215 -0
  58. minitap/mobile_use/servers/utils.py +11 -0
  59. minitap/mobile_use/services/accessibility.py +100 -0
  60. minitap/mobile_use/services/llm.py +143 -0
  61. minitap/mobile_use/tools/index.py +54 -0
  62. minitap/mobile_use/tools/mobile/back.py +52 -0
  63. minitap/mobile_use/tools/mobile/copy_text_from.py +77 -0
  64. minitap/mobile_use/tools/mobile/erase_text.py +124 -0
  65. minitap/mobile_use/tools/mobile/input_text.py +74 -0
  66. minitap/mobile_use/tools/mobile/launch_app.py +59 -0
  67. minitap/mobile_use/tools/mobile/list_packages.py +78 -0
  68. minitap/mobile_use/tools/mobile/long_press_on.py +62 -0
  69. minitap/mobile_use/tools/mobile/open_link.py +59 -0
  70. minitap/mobile_use/tools/mobile/paste_text.py +66 -0
  71. minitap/mobile_use/tools/mobile/press_key.py +58 -0
  72. minitap/mobile_use/tools/mobile/run_flow.py +57 -0
  73. minitap/mobile_use/tools/mobile/stop_app.py +58 -0
  74. minitap/mobile_use/tools/mobile/swipe.py +56 -0
  75. minitap/mobile_use/tools/mobile/take_screenshot.py +70 -0
  76. minitap/mobile_use/tools/mobile/tap.py +66 -0
  77. minitap/mobile_use/tools/mobile/wait_for_animation_to_end.py +68 -0
  78. minitap/mobile_use/tools/tool_wrapper.py +33 -0
  79. minitap/mobile_use/utils/cli_helpers.py +40 -0
  80. minitap/mobile_use/utils/cli_selection.py +144 -0
  81. minitap/mobile_use/utils/conversations.py +31 -0
  82. minitap/mobile_use/utils/decorators.py +123 -0
  83. minitap/mobile_use/utils/errors.py +6 -0
  84. minitap/mobile_use/utils/file.py +13 -0
  85. minitap/mobile_use/utils/logger.py +184 -0
  86. minitap/mobile_use/utils/media.py +73 -0
  87. minitap/mobile_use/utils/recorder.py +55 -0
  88. minitap/mobile_use/utils/requests_utils.py +37 -0
  89. minitap/mobile_use/utils/shell_utils.py +20 -0
  90. minitap/mobile_use/utils/time.py +6 -0
  91. minitap/mobile_use/utils/ui_hierarchy.py +30 -0
  92. minitap_mobile_use-0.0.1.dev0.dist-info/METADATA +274 -0
  93. minitap_mobile_use-0.0.1.dev0.dist-info/RECORD +95 -0
  94. minitap_mobile_use-0.0.1.dev0.dist-info/WHEEL +4 -0
  95. 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()