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,124 @@
1
+ from typing import Optional
2
+
3
+ from langchain_core.messages import ToolMessage
4
+ from langchain_core.tools import tool
5
+ from langchain_core.tools.base import InjectedToolCallId
6
+ from langgraph.prebuilt import InjectedState
7
+ from langgraph.types import Command
8
+ from typing_extensions import Annotated
9
+
10
+ from minitap.mobile_use.controllers.mobile_command_controller import (
11
+ ScreenDataResponse,
12
+ WaitTimeout,
13
+ get_screen_data,
14
+ wait_for_animation_to_end,
15
+ )
16
+ from minitap.mobile_use.controllers.mobile_command_controller import (
17
+ erase_text as erase_text_controller,
18
+ )
19
+ from minitap.mobile_use.graph.state import State
20
+ from minitap.mobile_use.tools.tool_wrapper import ExecutorMetadata, ToolWrapper
21
+ from minitap.mobile_use.utils.ui_hierarchy import find_element_by_resource_id
22
+ from minitap.mobile_use.context import MobileUseContext
23
+
24
+
25
+ def get_erase_text_tool(ctx: MobileUseContext):
26
+ @tool
27
+ def erase_text(
28
+ tool_call_id: Annotated[str, InjectedToolCallId],
29
+ state: Annotated[State, InjectedState],
30
+ agent_thought: str,
31
+ input_text_resource_id: str,
32
+ executor_metadata: Optional[ExecutorMetadata],
33
+ nb_chars: Optional[int] = None,
34
+ ):
35
+ """
36
+ Erases up to `nb_chars` characters from the currently selected text field (default: 50).
37
+
38
+ iOS Note:
39
+ This may be flaky on iOS. As a workaround:
40
+ - long_press_on("<input id>")
41
+ - tap_on("Select All")
42
+ - erase_text()
43
+
44
+ Matches 'clearText' in search.
45
+ """
46
+ # value of text key from input_text_ressource_id
47
+ latest_ui_hierarchy = state.latest_ui_hierarchy
48
+ previous_text_value = None
49
+ new_text_value = None
50
+ nb_char_erased = -1
51
+ if latest_ui_hierarchy is not None:
52
+ text_input_element = find_element_by_resource_id(
53
+ ui_hierarchy=latest_ui_hierarchy, resource_id=input_text_resource_id
54
+ )
55
+ if text_input_element:
56
+ previous_text_value = text_input_element.get("text", None)
57
+
58
+ output = erase_text_controller(ctx=ctx, nb_chars=nb_chars)
59
+ has_failed = output is not None
60
+
61
+ wait_for_animation_to_end(ctx=ctx, timeout=WaitTimeout.MEDIUM)
62
+
63
+ screen_data: ScreenDataResponse = get_screen_data(screen_api_client=ctx.screen_api_client)
64
+ latest_ui_hierarchy = screen_data.elements
65
+
66
+ if not has_failed and latest_ui_hierarchy is not None:
67
+ text_input_element = find_element_by_resource_id(
68
+ ui_hierarchy=latest_ui_hierarchy, resource_id=input_text_resource_id
69
+ )
70
+ if text_input_element:
71
+ new_text_value = text_input_element.get("text", None)
72
+
73
+ if previous_text_value is not None and new_text_value is not None:
74
+ if previous_text_value == new_text_value:
75
+ has_failed = True
76
+ output = (
77
+ "Unable to erase text: text is very likely a placeholder."
78
+ " Thus, assuming the text input is empty."
79
+ )
80
+ else:
81
+ nb_char_erased = len(previous_text_value) - len(new_text_value)
82
+ tool_message = ToolMessage(
83
+ tool_call_id=tool_call_id,
84
+ content=erase_text_wrapper.on_failure_fn(output)
85
+ if has_failed
86
+ else erase_text_wrapper.on_success_fn(
87
+ nb_char_erased=nb_char_erased, new_text_value=new_text_value
88
+ ),
89
+ additional_kwargs={"error": output} if has_failed else {},
90
+ )
91
+
92
+ return Command(
93
+ update=erase_text_wrapper.handle_executor_state_fields(
94
+ ctx=ctx,
95
+ state=state,
96
+ executor_metadata=executor_metadata,
97
+ tool_message=tool_message,
98
+ is_failure=has_failed,
99
+ updates={
100
+ "agents_thoughts": [agent_thought],
101
+ "messages": [tool_message],
102
+ },
103
+ ),
104
+ )
105
+
106
+ return erase_text
107
+
108
+
109
+ def format_success_message(nb_char_erased: int, new_text_value: Optional[str]):
110
+ output = ""
111
+ if nb_char_erased == -1:
112
+ output = "Text erased successfully."
113
+ else:
114
+ output = f"Text erased successfully. {nb_char_erased} characters were erased."
115
+ if new_text_value is not None:
116
+ output += f" New text in the input is {new_text_value}."
117
+ return output
118
+
119
+
120
+ erase_text_wrapper = ToolWrapper(
121
+ tool_fn_getter=get_erase_text_tool,
122
+ on_success_fn=format_success_message,
123
+ on_failure_fn=lambda output: "Failed to erase text. " + output,
124
+ )
@@ -0,0 +1,74 @@
1
+ from typing import Optional
2
+
3
+ from langchain_core.messages import ToolMessage
4
+ from langchain_core.tools import tool
5
+ from langchain_core.tools.base import InjectedToolCallId
6
+ from langgraph.types import Command
7
+ from minitap.mobile_use.controllers.mobile_command_controller import (
8
+ input_text as input_text_controller,
9
+ )
10
+ from minitap.mobile_use.tools.tool_wrapper import ExecutorMetadata, ToolWrapper
11
+ from typing_extensions import Annotated
12
+ from minitap.mobile_use.graph.state import State
13
+ from langgraph.prebuilt import InjectedState
14
+ from minitap.mobile_use.context import MobileUseContext
15
+
16
+
17
+ def get_input_text_tool(ctx: MobileUseContext):
18
+ @tool
19
+ def input_text(
20
+ tool_call_id: Annotated[str, InjectedToolCallId],
21
+ state: Annotated[State, InjectedState],
22
+ agent_thought: str,
23
+ executor_metadata: Optional[ExecutorMetadata],
24
+ text: str,
25
+ ):
26
+ """
27
+ Inputs the specified text into the UI (works even if no field is focused).
28
+
29
+ Example:
30
+ - inputText: "Hello World"
31
+
32
+ Notes:
33
+ - Unicode not supported on Android.
34
+
35
+ Random Input Options:
36
+ - inputRandomEmail
37
+ - inputRandomPersonName
38
+ - inputRandomNumber (with optional 'length', default 8)
39
+ - inputRandomText (with optional 'length', default 8)
40
+
41
+ Tip:
42
+ Use `copyTextFrom` to reuse generated inputs in later steps.
43
+ """
44
+ output = input_text_controller(ctx=ctx, text=text)
45
+ has_failed = output is not None
46
+ tool_message = ToolMessage(
47
+ tool_call_id=tool_call_id,
48
+ content=input_text_wrapper.on_failure_fn(text)
49
+ if has_failed
50
+ else input_text_wrapper.on_success_fn(text),
51
+ additional_kwargs={"error": output} if has_failed else {},
52
+ )
53
+ return Command(
54
+ update=input_text_wrapper.handle_executor_state_fields(
55
+ ctx=ctx,
56
+ state=state,
57
+ executor_metadata=executor_metadata,
58
+ tool_message=tool_message,
59
+ is_failure=has_failed,
60
+ updates={
61
+ "agents_thoughts": [agent_thought],
62
+ "messages": [tool_message],
63
+ },
64
+ ),
65
+ )
66
+
67
+ return input_text
68
+
69
+
70
+ input_text_wrapper = ToolWrapper(
71
+ tool_fn_getter=get_input_text_tool,
72
+ on_success_fn=lambda text: f"Successfully typed {text}",
73
+ on_failure_fn=lambda text: f"Failed to input text {text}",
74
+ )
@@ -0,0 +1,59 @@
1
+ from typing import Optional
2
+
3
+ from langchain_core.messages import ToolMessage
4
+ from langchain_core.tools import tool
5
+ from langchain_core.tools.base import InjectedToolCallId
6
+ from langgraph.types import Command
7
+ from minitap.mobile_use.controllers.mobile_command_controller import (
8
+ launch_app as launch_app_controller,
9
+ )
10
+ from minitap.mobile_use.tools.tool_wrapper import ExecutorMetadata, ToolWrapper
11
+ from typing_extensions import Annotated
12
+ from minitap.mobile_use.context import MobileUseContext
13
+ from minitap.mobile_use.graph.state import State
14
+ from langgraph.prebuilt import InjectedState
15
+
16
+
17
+ def get_launch_app_tool(ctx: MobileUseContext):
18
+ @tool
19
+ def launch_app(
20
+ tool_call_id: Annotated[str, InjectedToolCallId],
21
+ state: Annotated[State, InjectedState],
22
+ agent_thought: str,
23
+ executor_metadata: Optional[ExecutorMetadata],
24
+ package_name: str,
25
+ ):
26
+ """
27
+ Launch an application on the device using the package name on Android, bundle id on iOS.
28
+ """
29
+ output = launch_app_controller(ctx=ctx, package_name=package_name)
30
+ has_failed = output is not None
31
+ tool_message = ToolMessage(
32
+ tool_call_id=tool_call_id,
33
+ content=launch_app_wrapper.on_failure_fn(package_name)
34
+ if has_failed
35
+ else launch_app_wrapper.on_success_fn(package_name),
36
+ additional_kwargs={"error": output} if has_failed else {},
37
+ )
38
+ return Command(
39
+ update=launch_app_wrapper.handle_executor_state_fields(
40
+ ctx=ctx,
41
+ state=state,
42
+ executor_metadata=executor_metadata,
43
+ tool_message=tool_message,
44
+ is_failure=has_failed,
45
+ updates={
46
+ "agents_thoughts": [agent_thought],
47
+ "messages": [tool_message],
48
+ },
49
+ ),
50
+ )
51
+
52
+ return launch_app
53
+
54
+
55
+ launch_app_wrapper = ToolWrapper(
56
+ tool_fn_getter=get_launch_app_tool,
57
+ on_success_fn=lambda package_name: f"App {package_name} launched successfully.",
58
+ on_failure_fn=lambda package_name: f"Failed to launch app {package_name}.",
59
+ )
@@ -0,0 +1,78 @@
1
+ from typing import Optional
2
+
3
+ from langchain_core.messages import ToolMessage
4
+ from langchain_core.tools import tool
5
+ from langchain_core.tools.base import InjectedToolCallId
6
+ from langgraph.prebuilt import InjectedState
7
+ from langgraph.types import Command
8
+ from minitap.mobile_use.agents.hopper.hopper import HopperOutput, hopper
9
+ from minitap.mobile_use.context import MobileUseContext
10
+ from minitap.mobile_use.controllers.platform_specific_commands_controller import (
11
+ list_packages as list_packages_command,
12
+ )
13
+ from minitap.mobile_use.graph.state import State
14
+ from minitap.mobile_use.tools.tool_wrapper import ExecutorMetadata, ToolWrapper
15
+ from typing_extensions import Annotated
16
+
17
+
18
+ def get_list_packages_tool(ctx: MobileUseContext):
19
+ @tool
20
+ async def list_packages(
21
+ tool_call_id: Annotated[str, InjectedToolCallId],
22
+ state: Annotated[State, InjectedState],
23
+ agent_thought: str,
24
+ executor_metadata: Optional[ExecutorMetadata],
25
+ ) -> Command:
26
+ """
27
+ Lists all the applications on the device.
28
+ Outputs the full package names list (android) or bundle ids list (IOS).
29
+ """
30
+ output: str = list_packages_command(ctx=ctx)
31
+ has_failed = False
32
+
33
+ try:
34
+ hopper_output: HopperOutput = await hopper(
35
+ ctx=ctx,
36
+ initial_goal=state.initial_goal,
37
+ messages=state.messages,
38
+ data=output,
39
+ )
40
+ tool_message = ToolMessage(
41
+ tool_call_id=tool_call_id,
42
+ content=list_packages_wrapper.on_success_fn()
43
+ + ": "
44
+ + hopper_output.step
45
+ + ": "
46
+ + hopper_output.output,
47
+ )
48
+ except Exception as e:
49
+ print("Failed to extract insights from data: " + str(e))
50
+ tool_message = ToolMessage(
51
+ tool_call_id=tool_call_id,
52
+ content=list_packages_wrapper.on_failure_fn(),
53
+ additional_kwargs={"output": output},
54
+ )
55
+ has_failed = True
56
+
57
+ return Command(
58
+ update=list_packages_wrapper.handle_executor_state_fields(
59
+ ctx=ctx,
60
+ state=state,
61
+ executor_metadata=executor_metadata,
62
+ tool_message=tool_message,
63
+ is_failure=has_failed,
64
+ updates={
65
+ "agents_thoughts": [agent_thought],
66
+ "messages": [tool_message],
67
+ },
68
+ ),
69
+ )
70
+
71
+ return list_packages
72
+
73
+
74
+ list_packages_wrapper = ToolWrapper(
75
+ tool_fn_getter=get_list_packages_tool,
76
+ on_success_fn=lambda: "Packages listed successfully.",
77
+ on_failure_fn=lambda: "Failed to list packages.",
78
+ )
@@ -0,0 +1,62 @@
1
+ from typing import Optional
2
+
3
+ from langchain_core.messages import ToolMessage
4
+ from langchain_core.tools import tool
5
+ from langchain_core.tools.base import InjectedToolCallId
6
+ from langgraph.prebuilt import InjectedState
7
+ from langgraph.types import Command
8
+ from minitap.mobile_use.context import MobileUseContext
9
+ from minitap.mobile_use.controllers.mobile_command_controller import SelectorRequest
10
+ from minitap.mobile_use.controllers.mobile_command_controller import (
11
+ long_press_on as long_press_on_controller,
12
+ )
13
+ from minitap.mobile_use.graph.state import State
14
+ from minitap.mobile_use.tools.tool_wrapper import ExecutorMetadata, ToolWrapper
15
+ from typing_extensions import Annotated
16
+
17
+
18
+ def get_long_press_on_tool(ctx: MobileUseContext):
19
+ @tool
20
+ def long_press_on(
21
+ tool_call_id: Annotated[str, InjectedToolCallId],
22
+ state: Annotated[State, InjectedState],
23
+ agent_thought: str,
24
+ executor_metadata: Optional[ExecutorMetadata],
25
+ selector_request: SelectorRequest,
26
+ index: Optional[int] = None,
27
+ ):
28
+ """
29
+ Long press on a UI element identified by the given selector.
30
+ An index can be specified to select a specific element if multiple are found.
31
+ """
32
+ output = long_press_on_controller(ctx=ctx, selector_request=selector_request, index=index)
33
+ has_failed = output is not None
34
+ tool_message = ToolMessage(
35
+ tool_call_id=tool_call_id,
36
+ content=long_press_on_wrapper.on_failure_fn()
37
+ if has_failed
38
+ else long_press_on_wrapper.on_success_fn(),
39
+ additional_kwargs={"error": output} if has_failed else {},
40
+ )
41
+ return Command(
42
+ update=long_press_on_wrapper.handle_executor_state_fields(
43
+ ctx=ctx,
44
+ state=state,
45
+ executor_metadata=executor_metadata,
46
+ tool_message=tool_message,
47
+ is_failure=has_failed,
48
+ updates={
49
+ "agents_thoughts": [agent_thought],
50
+ "messages": [tool_message],
51
+ },
52
+ ),
53
+ )
54
+
55
+ return long_press_on
56
+
57
+
58
+ long_press_on_wrapper = ToolWrapper(
59
+ tool_fn_getter=get_long_press_on_tool,
60
+ on_success_fn=lambda: "Long press on is successful.",
61
+ on_failure_fn=lambda: "Failed to long press on.",
62
+ )
@@ -0,0 +1,59 @@
1
+ from typing import Optional
2
+
3
+ from langchain_core.messages import ToolMessage
4
+ from langchain_core.tools import tool
5
+ from langchain_core.tools.base import InjectedToolCallId
6
+ from langgraph.prebuilt import InjectedState
7
+ from langgraph.types import Command
8
+ from minitap.mobile_use.context import MobileUseContext
9
+ from minitap.mobile_use.controllers.mobile_command_controller import (
10
+ open_link as open_link_controller,
11
+ )
12
+ from minitap.mobile_use.graph.state import State
13
+ from minitap.mobile_use.tools.tool_wrapper import ExecutorMetadata, ToolWrapper
14
+ from typing_extensions import Annotated
15
+
16
+
17
+ def get_open_link_tool(ctx: MobileUseContext):
18
+ @tool
19
+ def open_link(
20
+ tool_call_id: Annotated[str, InjectedToolCallId],
21
+ state: Annotated[State, InjectedState],
22
+ agent_thought: str,
23
+ executor_metadata: Optional[ExecutorMetadata],
24
+ url: str,
25
+ ):
26
+ """
27
+ Open a link on a device (i.e. a deep link).
28
+ """
29
+ output = open_link_controller(ctx=ctx, url=url)
30
+ has_failed = output is not None
31
+ tool_message = ToolMessage(
32
+ tool_call_id=tool_call_id,
33
+ content=open_link_wrapper.on_failure_fn()
34
+ if has_failed
35
+ else open_link_wrapper.on_success_fn(url),
36
+ additional_kwargs={"error": output} if has_failed else {},
37
+ )
38
+ return Command(
39
+ update=open_link_wrapper.handle_executor_state_fields(
40
+ ctx=ctx,
41
+ state=state,
42
+ executor_metadata=executor_metadata,
43
+ tool_message=tool_message,
44
+ is_failure=has_failed,
45
+ updates={
46
+ "agents_thoughts": [agent_thought],
47
+ "messages": [tool_message],
48
+ },
49
+ ),
50
+ )
51
+
52
+ return open_link
53
+
54
+
55
+ open_link_wrapper = ToolWrapper(
56
+ tool_fn_getter=get_open_link_tool,
57
+ on_success_fn=lambda url: f"Link {url} opened successfully.",
58
+ on_failure_fn=lambda: "Failed to open link.",
59
+ )
@@ -0,0 +1,66 @@
1
+ from typing import Optional
2
+
3
+ from langchain_core.messages import ToolMessage
4
+ from langchain_core.tools import tool
5
+ from langchain_core.tools.base import InjectedToolCallId
6
+ from langgraph.types import Command
7
+ from minitap.mobile_use.context import MobileUseContext
8
+ from minitap.mobile_use.controllers.mobile_command_controller import (
9
+ paste_text as paste_text_controller,
10
+ )
11
+ from minitap.mobile_use.graph.state import State
12
+ from langgraph.prebuilt import InjectedState
13
+ from minitap.mobile_use.tools.tool_wrapper import ExecutorMetadata, ToolWrapper
14
+ from typing_extensions import Annotated
15
+
16
+
17
+ def get_paste_text_tool(ctx: MobileUseContext):
18
+ @tool
19
+ def paste_text(
20
+ tool_call_id: Annotated[str, InjectedToolCallId],
21
+ state: Annotated[State, InjectedState],
22
+ agent_thought: str,
23
+ executor_metadata: Optional[ExecutorMetadata],
24
+ ):
25
+ """
26
+ Pastes text previously copied via `copyTextFrom` into the currently focused field.
27
+
28
+ Note:
29
+ The text field must be focused before using this command.
30
+
31
+ Example:
32
+ - copyTextFrom: { id: "someId" }
33
+ - tapOn: { id: "searchFieldId" }
34
+ - pasteText
35
+ """
36
+ output = paste_text_controller(ctx=ctx)
37
+ has_failed = output is not None
38
+ tool_message = ToolMessage(
39
+ tool_call_id=tool_call_id,
40
+ content=paste_text_wrapper.on_failure_fn()
41
+ if has_failed
42
+ else paste_text_wrapper.on_success_fn(),
43
+ additional_kwargs={"error": output} if has_failed else {},
44
+ )
45
+ return Command(
46
+ update=paste_text_wrapper.handle_executor_state_fields(
47
+ ctx=ctx,
48
+ state=state,
49
+ executor_metadata=executor_metadata,
50
+ tool_message=tool_message,
51
+ is_failure=has_failed,
52
+ updates={
53
+ "agents_thoughts": [agent_thought],
54
+ "messages": [tool_message],
55
+ },
56
+ ),
57
+ )
58
+
59
+ return paste_text
60
+
61
+
62
+ paste_text_wrapper = ToolWrapper(
63
+ tool_fn_getter=get_paste_text_tool,
64
+ on_success_fn=lambda: "Text pasted successfully.",
65
+ on_failure_fn=lambda: "Failed to paste text.",
66
+ )
@@ -0,0 +1,58 @@
1
+ from typing import Optional
2
+
3
+ from langchain_core.messages import ToolMessage
4
+ from langchain_core.tools import tool
5
+ from langchain_core.tools.base import InjectedToolCallId
6
+ from langgraph.prebuilt import InjectedState
7
+ from langgraph.types import Command
8
+ from minitap.mobile_use.context import MobileUseContext
9
+ from minitap.mobile_use.controllers.mobile_command_controller import Key
10
+ from minitap.mobile_use.controllers.mobile_command_controller import (
11
+ press_key as press_key_controller,
12
+ )
13
+ from minitap.mobile_use.graph.state import State
14
+ from minitap.mobile_use.tools.tool_wrapper import ExecutorMetadata, ToolWrapper
15
+ from typing_extensions import Annotated
16
+
17
+
18
+ def get_press_key_tool(ctx: MobileUseContext):
19
+ @tool
20
+ def press_key(
21
+ tool_call_id: Annotated[str, InjectedToolCallId],
22
+ state: Annotated[State, InjectedState],
23
+ agent_thought: str,
24
+ executor_metadata: Optional[ExecutorMetadata],
25
+ key: Key,
26
+ ):
27
+ """Press a key on the device."""
28
+ output = press_key_controller(ctx=ctx, key=key)
29
+ has_failed = output is not None
30
+ tool_message = ToolMessage(
31
+ tool_call_id=tool_call_id,
32
+ content=press_key_wrapper.on_failure_fn(key)
33
+ if has_failed
34
+ else press_key_wrapper.on_success_fn(key),
35
+ additional_kwargs={"error": output} if has_failed else {},
36
+ )
37
+ return Command(
38
+ update=press_key_wrapper.handle_executor_state_fields(
39
+ ctx=ctx,
40
+ state=state,
41
+ executor_metadata=executor_metadata,
42
+ tool_message=tool_message,
43
+ is_failure=has_failed,
44
+ updates={
45
+ "agents_thoughts": [agent_thought],
46
+ "messages": [tool_message],
47
+ },
48
+ ),
49
+ )
50
+
51
+ return press_key
52
+
53
+
54
+ press_key_wrapper = ToolWrapper(
55
+ tool_fn_getter=get_press_key_tool,
56
+ on_success_fn=lambda key: f"Key {key.value} pressed successfully.",
57
+ on_failure_fn=lambda key: f"Failed to press key {key.value}.",
58
+ )
@@ -0,0 +1,57 @@
1
+ from typing import Optional
2
+ from langchain_core.messages import ToolMessage
3
+ from langchain_core.tools import tool
4
+ from langchain_core.tools.base import InjectedToolCallId
5
+ from langgraph.prebuilt import InjectedState
6
+ from langgraph.types import Command
7
+ from minitap.mobile_use.context import MobileUseContext
8
+ from minitap.mobile_use.controllers.mobile_command_controller import run_flow as run_flow_controller
9
+ from minitap.mobile_use.graph.state import State
10
+ from minitap.mobile_use.tools.tool_wrapper import ExecutorMetadata, ToolWrapper
11
+ from typing_extensions import Annotated
12
+
13
+
14
+ def get_run_flow_tool(ctx: MobileUseContext):
15
+ @tool
16
+ def run_flow(
17
+ tool_call_id: Annotated[str, InjectedToolCallId],
18
+ state: Annotated[State, InjectedState],
19
+ agent_thought: str,
20
+ flow_steps: list,
21
+ executor_metadata: Optional[ExecutorMetadata],
22
+ dry_run: bool = False,
23
+ ):
24
+ """
25
+ Run a flow i.e, a sequence of commands.
26
+ """
27
+ output = run_flow_controller(ctx=ctx, flow_steps=flow_steps, dry_run=dry_run)
28
+ has_failed = output is not None
29
+ tool_message = ToolMessage(
30
+ tool_call_id=tool_call_id,
31
+ content=run_flow_wrapper.on_failure_fn()
32
+ if has_failed
33
+ else run_flow_wrapper.on_success_fn(),
34
+ additional_kwargs={"error": output} if has_failed else {},
35
+ )
36
+ return Command(
37
+ update=run_flow_wrapper.handle_executor_state_fields(
38
+ ctx=ctx,
39
+ state=state,
40
+ executor_metadata=executor_metadata,
41
+ tool_message=tool_message,
42
+ is_failure=has_failed,
43
+ updates={
44
+ "agents_thoughts": [agent_thought],
45
+ "messages": [tool_message],
46
+ },
47
+ ),
48
+ )
49
+
50
+ return run_flow
51
+
52
+
53
+ run_flow_wrapper = ToolWrapper(
54
+ tool_fn_getter=get_run_flow_tool,
55
+ on_success_fn=lambda: "Flow run successfully.",
56
+ on_failure_fn=lambda: "Failed to run flow.",
57
+ )