Skip to content

Commit fd40e6b

Browse files
feat(tools): add LoadImageTool and improve file tool flexibility
1 parent 7886049 commit fd40e6b

9 files changed

Lines changed: 213 additions & 72 deletions

File tree

src/askui/models/shared/tools.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,9 @@ def _create_tool_result_block_param_for_playwright_error(
163163

164164

165165
class Tool(BaseModel, ABC):
166-
model_config = ConfigDict(populate_by_name=True)
166+
model_config = ConfigDict(
167+
validate_by_alias=True,
168+
)
167169

168170
base_name: str = Field(alias="name", description="Name of the tool")
169171
description: str = Field(description="Description of what the tool does")

src/askui/tools/askui/askui_controller.py

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ def _run_recorder_action(
225225
) -> controller_v1_pbs.Response_RunRecordedAction:
226226
time.sleep(self._pre_action_wait)
227227
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
228-
"Stub is not initialized. Call Connect first."
228+
"Stub is not initialized. Call `connect()` first."
229229
)
230230
response: controller_v1_pbs.Response_RunRecordedAction = (
231231
self._stub.RunRecordedAction(
@@ -241,7 +241,7 @@ def _run_recorder_action(
241241
num_retries = 0
242242
for _ in range(self._max_retries):
243243
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
244-
"Stub is not initialized. Call Connect first."
244+
"Stub is not initialized. Call `connect()` first."
245245
)
246246
poll_response: controller_v1_pbs.Response_Poll = self._stub.Poll(
247247
controller_v1_pbs.Request_Poll(
@@ -313,7 +313,7 @@ def __exit__(
313313

314314
def _start_session(self) -> None:
315315
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
316-
"Stub is not initialized. Call Connect first."
316+
"Stub is not initialized. Call `connect()` first."
317317
)
318318
response = self._stub.StartSession(
319319
controller_v1_pbs.Request_StartSession(
@@ -324,23 +324,23 @@ def _start_session(self) -> None:
324324

325325
def _stop_session(self) -> None:
326326
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
327-
"Stub is not initialized. Call Connect first."
327+
"Stub is not initialized. Call `connect()` first."
328328
)
329329
self._stub.EndSession(
330330
controller_v1_pbs.Request_EndSession(sessionInfo=self._session_info)
331331
)
332332

333333
def _start_execution(self) -> None:
334334
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
335-
"Stub is not initialized. Call Connect first."
335+
"Stub is not initialized. Call `connect()` first."
336336
)
337337
self._stub.StartExecution(
338338
controller_v1_pbs.Request_StartExecution(sessionInfo=self._session_info)
339339
)
340340

341341
def _stop_execution(self) -> None:
342342
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
343-
"Stub is not initialized. Call Connect first."
343+
"Stub is not initialized. Call `connect()` first."
344344
)
345345
self._stub.StopExecution(
346346
controller_v1_pbs.Request_StopExecution(sessionInfo=self._session_info)
@@ -361,7 +361,7 @@ def screenshot(self, report: bool = True) -> Image.Image:
361361
362362
"""
363363
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
364-
"Stub is not initialized. Call Connect first."
364+
"Stub is not initialized. Call `connect()` first."
365365
)
366366
screenResponse = self._stub.CaptureScreen(
367367
controller_v1_pbs.Request_CaptureScreen(
@@ -652,7 +652,7 @@ def set_display(self, display: int = 1) -> None:
652652
Defaults to `1`.
653653
"""
654654
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
655-
"Stub is not initialized. Call Connect first."
655+
"Stub is not initialized. Call `connect()` first."
656656
)
657657
self._stub.SetActiveDisplay(
658658
controller_v1_pbs.Request_SetActiveDisplay(displayID=display)
@@ -715,7 +715,7 @@ def list_displays(
715715
DisplaysListResponse
716716
"""
717717
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
718-
"Stub is not initialized. Call Connect first."
718+
"Stub is not initialized. Call `connect()` first."
719719
)
720720

721721
self._reporter.add_message("AgentOS", "list_displays()")
@@ -752,7 +752,7 @@ def get_process_list(
752752
- processes: List of ProcessInfo objects
753753
"""
754754
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
755-
"Stub is not initialized. Call Connect first."
755+
"Stub is not initialized. Call `connect()` first."
756756
)
757757

758758
self._reporter.add_message("AgentOS", f"get_process_list({get_extended_info})")
@@ -781,7 +781,7 @@ def get_window_list(
781781
- windows: List of WindowInfo objects with ID and name
782782
"""
783783
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
784-
"Stub is not initialized. Call Connect first."
784+
"Stub is not initialized. Call `connect()` first."
785785
)
786786

787787
self._reporter.add_message("AgentOS", f"get_window_list({process_id})")
@@ -809,7 +809,7 @@ def get_automation_target_list(
809809
- targets: List of AutomationTarget objects
810810
"""
811811
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
812-
"Stub is not initialized. Call Connect first."
812+
"Stub is not initialized. Call `connect()` first."
813813
)
814814

815815
self._reporter.add_message("AgentOS", "get_automation_target_list()")
@@ -832,7 +832,7 @@ def set_mouse_delay(self, delay_ms: int) -> None:
832832
delay_ms (int): The delay in milliseconds to set for mouse actions.
833833
"""
834834
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
835-
"Stub is not initialized. Call Connect first."
835+
"Stub is not initialized. Call `connect()` first."
836836
)
837837

838838
self._reporter.add_message("AgentOS", f"set_mouse_delay({delay_ms})")
@@ -852,7 +852,7 @@ def set_keyboard_delay(self, delay_ms: int) -> None:
852852
delay_ms (int): The delay in milliseconds to set for keyboard actions.
853853
"""
854854
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
855-
"Stub is not initialized. Call Connect first."
855+
"Stub is not initialized. Call `connect()` first."
856856
)
857857

858858
self._reporter.add_message("AgentOS", f"set_keyboard_delay({delay_ms})")
@@ -881,7 +881,7 @@ def set_active_window(self, process_id: int, window_id: int) -> int:
881881
If display length is not increased after adding the window.
882882
"""
883883
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
884-
"Stub is not initialized. Call Connect first."
884+
"Stub is not initialized. Call `connect()` first."
885885
)
886886

887887
self._reporter.add_message(
@@ -914,7 +914,7 @@ def set_active_automation_target(self, target_id: int) -> None:
914914
target_id (int): The ID of the automation target to set as active.
915915
"""
916916
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
917-
"Stub is not initialized. Call Connect first."
917+
"Stub is not initialized. Call `connect()` first."
918918
)
919919

920920
self._reporter.add_message(
@@ -945,7 +945,7 @@ def schedule_batched_action(
945945
the scheduled action ID.
946946
"""
947947
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
948-
"Stub is not initialized. Call Connect first."
948+
"Stub is not initialized. Call `connect()` first."
949949
)
950950

951951
self._reporter.add_message(
@@ -971,7 +971,7 @@ def start_batch_run(self) -> None:
971971
Start executing batched actions.
972972
"""
973973
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
974-
"Stub is not initialized. Call Connect first."
974+
"Stub is not initialized. Call `connect()` first."
975975
)
976976

977977
self._reporter.add_message("AgentOS", "start_batch_run()")
@@ -986,7 +986,7 @@ def stop_batch_run(self) -> None:
986986
Stop executing batched actions.
987987
"""
988988
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
989-
"Stub is not initialized. Call Connect first."
989+
"Stub is not initialized. Call `connect()` first."
990990
)
991991

992992
self._reporter.add_message("AgentOS", "stop_batch_run()")
@@ -1005,7 +1005,7 @@ def get_action_count(self) -> controller_v1_pbs.Response_GetActionCount:
10051005
containing the action count.
10061006
"""
10071007
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
1008-
"Stub is not initialized. Call Connect first."
1008+
"Stub is not initialized. Call `connect()` first."
10091009
)
10101010

10111011
self._reporter.add_message("AgentOS", "get_action_count()")
@@ -1031,7 +1031,7 @@ def get_action(self, action_index: int) -> controller_v1_pbs.Response_GetAction:
10311031
- actionParameters: The action parameters
10321032
"""
10331033
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
1034-
"Stub is not initialized. Call Connect first."
1034+
"Stub is not initialized. Call `connect()` first."
10351035
)
10361036

10371037
self._reporter.add_message("AgentOS", f"get_action({action_index})")
@@ -1053,7 +1053,7 @@ def remove_action(self, action_id: int) -> None:
10531053
action_id (int): The ID of the action to remove.
10541054
"""
10551055
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
1056-
"Stub is not initialized. Call Connect first."
1056+
"Stub is not initialized. Call `connect()` first."
10571057
)
10581058

10591059
self._reporter.add_message("AgentOS", f"remove_action({action_id})")
@@ -1070,7 +1070,7 @@ def remove_all_actions(self) -> None:
10701070
Clear all recorded or batched actions.
10711071
"""
10721072
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
1073-
"Stub is not initialized. Call Connect first."
1073+
"Stub is not initialized. Call `connect()` first."
10741074
)
10751075

10761076
self._reporter.add_message("AgentOS", "remove_all_actions()")
@@ -1095,7 +1095,7 @@ def _send_command(self, command: Command) -> AskUIAgentOSSendResponseSchema:
10951095
on the server side.
10961096
"""
10971097
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
1098-
"Stub is not initialized. Call Connect first."
1098+
"Stub is not initialized. Call `connect()` first."
10991099
)
11001100

11011101
header = Header(authentication=Guid(root=self._session_guid))
@@ -1126,7 +1126,7 @@ def get_mouse_position(self) -> Coordinate:
11261126
Coordinate: Response containing the result of the mouse position change.
11271127
"""
11281128
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
1129-
"Stub is not initialized. Call Connect first."
1129+
"Stub is not initialized. Call `connect()` first."
11301130
)
11311131
self._reporter.add_message("AgentOS", "get_mouse_position()")
11321132
res = self._send_command(GetMousePositionCommand())
@@ -1147,7 +1147,7 @@ def set_mouse_position(self, x: int, y: int) -> None:
11471147
y (int): The vertical coordinate (in pixels) to set the cursor to.
11481148
"""
11491149
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
1150-
"Stub is not initialized. Call Connect first."
1150+
"Stub is not initialized. Call `connect()` first."
11511151
)
11521152
location = Location(x=Length(root=x), y=Length(root=y))
11531153
command = SetMousePositionCommand(parameters=[location])
@@ -1166,7 +1166,7 @@ def render_quad(self, style: RenderObjectStyle) -> int:
11661166
int: Object ID.
11671167
"""
11681168
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
1169-
"Stub is not initialized. Call Connect first."
1169+
"Stub is not initialized. Call `connect()` first."
11701170
)
11711171
self._reporter.add_message("AgentOS", f"render_quad({style})")
11721172
command = AddRenderObjectCommand(parameters=["Quad", style])
@@ -1186,7 +1186,7 @@ def render_line(self, style: RenderObjectStyle, points: list[Coordinate]) -> int
11861186
int: Object ID.
11871187
"""
11881188
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
1189-
"Stub is not initialized. Call Connect first."
1189+
"Stub is not initialized. Call `connect()` first."
11901190
)
11911191
self._reporter.add_message("AgentOS", f"render_line({style}, {points})")
11921192
command = AddRenderObjectCommand(parameters=["Line", style, points])
@@ -1206,7 +1206,7 @@ def render_image(self, style: RenderObjectStyle, image_data: str) -> int:
12061206
int: Object ID.
12071207
"""
12081208
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
1209-
"Stub is not initialized. Call Connect first."
1209+
"Stub is not initialized. Call `connect()` first."
12101210
)
12111211
self._reporter.add_message("AgentOS", f"render_image({style}, [image_data])")
12121212
image = RenderImage(root=image_data)
@@ -1228,7 +1228,7 @@ def render_text(self, style: RenderObjectStyle, content: str) -> int:
12281228
int: Object ID.
12291229
"""
12301230
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
1231-
"Stub is not initialized. Call Connect first."
1231+
"Stub is not initialized. Call `connect()` first."
12321232
)
12331233
self._reporter.add_message("AgentOS", f"render_text({style}, {content})")
12341234
text = RenderText(root=content)
@@ -1249,7 +1249,7 @@ def update_render_object(self, object_id: int, style: RenderObjectStyle) -> None
12491249
int: Object ID.
12501250
"""
12511251
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
1252-
"Stub is not initialized. Call Connect first."
1252+
"Stub is not initialized. Call `connect()` first."
12531253
)
12541254
self._reporter.add_message(
12551255
"AgentOS", f"update_render_object({object_id}, {style})"
@@ -1267,7 +1267,7 @@ def delete_render_object(self, object_id: int) -> None:
12671267
object_id (RenderObjectId): The ID of the render object to delete.
12681268
"""
12691269
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
1270-
"Stub is not initialized. Call Connect first."
1270+
"Stub is not initialized. Call `connect()` first."
12711271
)
12721272
self._reporter.add_message("AgentOS", f"delete_render_object({object_id})")
12731273
render_object_id = RenderObjectId(root=object_id)
@@ -1280,7 +1280,7 @@ def clear_render_objects(self) -> None:
12801280
Clear all render objects from the display.
12811281
"""
12821282
assert isinstance(self._stub, controller_v1.ControllerAPIStub), (
1283-
"Stub is not initialized. Call Connect first."
1283+
"Stub is not initialized. Call `connect()` first."
12841284
)
12851285
self._reporter.add_message("AgentOS", "clear_render_objects()")
12861286
command = ClearRenderObjectsCommand()

src/askui/tools/askui/askui_controller_settings.py

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
import sys
33
from functools import cached_property
44

5-
from pydantic import BaseModel, Field, field_validator, model_validator
5+
from pydantic import BaseModel, Field, field_validator
66
from pydantic_settings import BaseSettings, SettingsConfigDict
7-
from typing_extensions import Self
87

98

109
class RemoteDeviceController(BaseModel):
@@ -93,22 +92,6 @@ def validate_controller_args(cls, value: str) -> str:
9392

9493
return value
9594

96-
@model_validator(mode="after")
97-
def validate_either_component_registry_or_installation_directory_is_set(
98-
self,
99-
) -> "Self":
100-
if (
101-
self.component_registry_file is None
102-
and self.installation_directory is None
103-
and self.controller_path_setting is None
104-
):
105-
error_msg = (
106-
"Either ASKUI_COMPONENT_REGISTRY_FILE, ASKUI_INSTALLATION_DIRECTORY, "
107-
"or ASKUI_CONTROLLER_PATH environment variable must be set"
108-
)
109-
raise ValueError(error_msg)
110-
return self
111-
11295
def _find_remote_device_controller_by_installation_directory(
11396
self,
11497
) -> pathlib.Path | None:

src/askui/tools/store/universal/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66

77
from .list_files_tool import ListFilesTool
8+
from .load_image_tool import LoadImageTool
89
from .print_to_console import PrintToConsoleTool
910
from .read_from_file_tool import ReadFromFileTool
1011
from .write_to_file_tool import WriteToFileTool
@@ -14,4 +15,5 @@
1415
"PrintToConsoleTool",
1516
"ReadFromFileTool",
1617
"WriteToFileTool",
18+
"LoadImageTool",
1719
]

src/askui/tools/store/universal/list_files_tool.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class ListFilesTool(Tool):
1313
directories during execution.
1414
1515
Args:
16-
base_dir (str): The base directory path where file listing will start.
16+
base_dir (str | Path): The base directory path where file listing will start.
1717
All directory paths will be relative to this directory.
1818
1919
Example:
@@ -29,7 +29,10 @@ class ListFilesTool(Tool):
2929
```
3030
"""
3131

32-
def __init__(self, base_dir: str) -> None:
32+
def __init__(self, base_dir: str | Path) -> None:
33+
if not isinstance(base_dir, Path):
34+
base_dir = Path(base_dir)
35+
base_dir = base_dir.absolute()
3336
super().__init__(
3437
name="list_files_tool",
3538
description=(
@@ -66,7 +69,7 @@ def __init__(self, base_dir: str) -> None:
6669
"required": [],
6770
},
6871
)
69-
self._base_dir = Path(base_dir)
72+
self._base_dir = base_dir
7073

7174
def __call__(self, directory_path: str = "", recursive: bool = False) -> str:
7275
"""

0 commit comments

Comments
 (0)