inspect-ai 0.3.71__py3-none-any.whl → 0.3.73__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.
- inspect_ai/_cli/eval.py +14 -3
- inspect_ai/_cli/sandbox.py +3 -3
- inspect_ai/_cli/score.py +6 -4
- inspect_ai/_cli/trace.py +53 -6
- inspect_ai/_display/core/config.py +1 -1
- inspect_ai/_display/core/display.py +2 -1
- inspect_ai/_display/core/footer.py +6 -6
- inspect_ai/_display/plain/display.py +11 -6
- inspect_ai/_display/rich/display.py +23 -13
- inspect_ai/_display/textual/app.py +10 -9
- inspect_ai/_display/textual/display.py +2 -2
- inspect_ai/_display/textual/widgets/footer.py +4 -0
- inspect_ai/_display/textual/widgets/samples.py +14 -5
- inspect_ai/_eval/context.py +1 -2
- inspect_ai/_eval/eval.py +54 -41
- inspect_ai/_eval/loader.py +9 -2
- inspect_ai/_eval/run.py +148 -81
- inspect_ai/_eval/score.py +13 -8
- inspect_ai/_eval/task/images.py +31 -21
- inspect_ai/_eval/task/run.py +62 -59
- inspect_ai/_eval/task/rundir.py +16 -9
- inspect_ai/_eval/task/sandbox.py +7 -8
- inspect_ai/_eval/task/util.py +7 -0
- inspect_ai/_util/_async.py +118 -10
- inspect_ai/_util/constants.py +0 -2
- inspect_ai/_util/file.py +15 -29
- inspect_ai/_util/future.py +37 -0
- inspect_ai/_util/http.py +3 -99
- inspect_ai/_util/httpx.py +60 -0
- inspect_ai/_util/interrupt.py +2 -2
- inspect_ai/_util/json.py +5 -52
- inspect_ai/_util/logger.py +30 -86
- inspect_ai/_util/retry.py +10 -61
- inspect_ai/_util/trace.py +2 -2
- inspect_ai/_view/server.py +86 -3
- inspect_ai/_view/www/dist/assets/index.js +25837 -13269
- inspect_ai/_view/www/log-schema.json +253 -186
- inspect_ai/_view/www/package.json +2 -2
- inspect_ai/_view/www/src/plan/PlanDetailView.tsx +8 -3
- inspect_ai/_view/www/src/samples/transcript/StepEventView.tsx +2 -3
- inspect_ai/_view/www/src/types/log.d.ts +122 -94
- inspect_ai/approval/_human/manager.py +6 -10
- inspect_ai/approval/_human/panel.py +2 -2
- inspect_ai/dataset/_sources/util.py +7 -6
- inspect_ai/log/__init__.py +4 -0
- inspect_ai/log/_file.py +35 -61
- inspect_ai/log/_log.py +18 -1
- inspect_ai/log/_recorders/eval.py +14 -23
- inspect_ai/log/_recorders/json.py +3 -18
- inspect_ai/log/_samples.py +27 -2
- inspect_ai/log/_transcript.py +8 -8
- inspect_ai/model/__init__.py +2 -1
- inspect_ai/model/_call_tools.py +60 -40
- inspect_ai/model/_chat_message.py +3 -2
- inspect_ai/model/_generate_config.py +25 -0
- inspect_ai/model/_model.py +74 -36
- inspect_ai/model/_openai.py +9 -1
- inspect_ai/model/_providers/anthropic.py +172 -154
- inspect_ai/model/_providers/azureai.py +11 -9
- inspect_ai/model/_providers/bedrock.py +33 -24
- inspect_ai/model/_providers/cloudflare.py +8 -9
- inspect_ai/model/_providers/goodfire.py +7 -3
- inspect_ai/model/_providers/google.py +47 -13
- inspect_ai/model/_providers/groq.py +15 -15
- inspect_ai/model/_providers/hf.py +24 -17
- inspect_ai/model/_providers/mistral.py +36 -20
- inspect_ai/model/_providers/openai.py +30 -25
- inspect_ai/model/_providers/openai_o1.py +1 -1
- inspect_ai/model/_providers/providers.py +1 -1
- inspect_ai/model/_providers/together.py +3 -4
- inspect_ai/model/_providers/util/__init__.py +2 -2
- inspect_ai/model/_providers/util/chatapi.py +6 -19
- inspect_ai/model/_providers/util/hooks.py +165 -0
- inspect_ai/model/_providers/vertex.py +20 -3
- inspect_ai/model/_providers/vllm.py +16 -19
- inspect_ai/scorer/_multi.py +5 -2
- inspect_ai/solver/_bridge/patch.py +31 -1
- inspect_ai/solver/_fork.py +5 -3
- inspect_ai/solver/_human_agent/agent.py +3 -2
- inspect_ai/tool/__init__.py +8 -2
- inspect_ai/tool/_tool_info.py +4 -90
- inspect_ai/tool/_tool_params.py +4 -34
- inspect_ai/tool/_tools/_computer/_common.py +117 -58
- inspect_ai/tool/_tools/_computer/_computer.py +80 -57
- inspect_ai/tool/_tools/_computer/_resources/image_home_dir/.config/Code/User/settings.json +7 -1
- inspect_ai/tool/_tools/_computer/_resources/image_home_dir/.config/xfce4/xfconf/xfce-perchannel-xml/xfwm4.xml +91 -0
- inspect_ai/tool/_tools/_computer/_resources/tool/.pylintrc +8 -0
- inspect_ai/tool/_tools/_computer/_resources/tool/.vscode/settings.json +12 -0
- inspect_ai/tool/_tools/_computer/_resources/tool/_args.py +78 -0
- inspect_ai/tool/_tools/_computer/_resources/tool/_constants.py +20 -0
- inspect_ai/tool/_tools/_computer/_resources/tool/_x11_client.py +175 -113
- inspect_ai/tool/_tools/_computer/_resources/tool/computer_tool.py +76 -20
- inspect_ai/tool/_tools/_computer/_resources/tool/pyproject.toml +65 -0
- inspect_ai/tool/_tools/_computer/test_args.py +151 -0
- inspect_ai/tool/_tools/_web_search.py +30 -24
- inspect_ai/util/__init__.py +4 -0
- inspect_ai/util/_concurrency.py +5 -6
- inspect_ai/util/_display.py +6 -0
- inspect_ai/util/_json.py +170 -0
- inspect_ai/util/_sandbox/docker/cleanup.py +13 -9
- inspect_ai/util/_sandbox/docker/docker.py +5 -0
- inspect_ai/util/_sandbox/environment.py +56 -9
- inspect_ai/util/_sandbox/service.py +12 -5
- inspect_ai/util/_subprocess.py +94 -113
- inspect_ai/util/_subtask.py +2 -4
- {inspect_ai-0.3.71.dist-info → inspect_ai-0.3.73.dist-info}/METADATA +6 -2
- {inspect_ai-0.3.71.dist-info → inspect_ai-0.3.73.dist-info}/RECORD +111 -103
- {inspect_ai-0.3.71.dist-info → inspect_ai-0.3.73.dist-info}/WHEEL +1 -1
- inspect_ai/_util/timeouts.py +0 -160
- inspect_ai/model/_providers/util/tracker.py +0 -92
- inspect_ai/tool/_tools/_computer/_computer_split.py +0 -198
- {inspect_ai-0.3.71.dist-info → inspect_ai-0.3.73.dist-info}/LICENSE +0 -0
- {inspect_ai-0.3.71.dist-info → inspect_ai-0.3.73.dist-info}/entry_points.txt +0 -0
- {inspect_ai-0.3.71.dist-info → inspect_ai-0.3.73.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
"""
|
1
|
+
"""Inspired by https://github.com/anthropics/anthropic-quickstarts/blob/main/computer-use-demo/computer_use_demo/tools/computer.py"""
|
2
2
|
|
3
3
|
import asyncio
|
4
4
|
import base64
|
@@ -19,21 +19,8 @@ TYPING_GROUP_SIZE = 50
|
|
19
19
|
|
20
20
|
ColorCount = Literal[4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4]
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
"type",
|
25
|
-
"mouse_move",
|
26
|
-
"left_click",
|
27
|
-
"left_click_drag",
|
28
|
-
"right_click",
|
29
|
-
"middle_click",
|
30
|
-
"double_click",
|
31
|
-
"screenshot",
|
32
|
-
"cursor_position",
|
33
|
-
]
|
34
|
-
|
35
|
-
|
36
|
-
class ToolError(Exception):
|
22
|
+
|
23
|
+
class X11ClientError(Exception):
|
37
24
|
def __init__(self, message):
|
38
25
|
self.message = message
|
39
26
|
|
@@ -83,7 +70,7 @@ class X11Client:
|
|
83
70
|
|
84
71
|
@property
|
85
72
|
def options(self) -> ComputerToolOptions:
|
86
|
-
width, height = self.
|
73
|
+
width, height = self._scale_coordinates("computer", self.width, self.height)
|
87
74
|
return {
|
88
75
|
"display_width_px": width,
|
89
76
|
"display_height_px": height,
|
@@ -105,120 +92,191 @@ class X11Client:
|
|
105
92
|
|
106
93
|
self.xdotool = f"{self._display_prefix}xdotool"
|
107
94
|
|
108
|
-
async def
|
95
|
+
async def key(self, text: str) -> ToolResult:
|
96
|
+
return await self._shell(f"{self.xdotool} key -- {_key_arg_for_text(text)}")
|
97
|
+
|
98
|
+
async def hold_key(self, text: str, duration: int) -> ToolResult:
|
99
|
+
key_arg = _key_arg_for_text(text)
|
100
|
+
await self._shell(f"{self.xdotool} keydown -- {key_arg}", False)
|
101
|
+
await asyncio.sleep(duration)
|
102
|
+
return await self._shell(f"{self.xdotool} keyup -- {key_arg}")
|
103
|
+
|
104
|
+
async def type(self, text: str) -> ToolResult:
|
105
|
+
results: list[ToolResult] = []
|
106
|
+
for chunk in chunks(text, TYPING_GROUP_SIZE):
|
107
|
+
cmd = (
|
108
|
+
f"{self.xdotool} type --delay {TYPING_DELAY_MS} -- {shlex.quote(chunk)}"
|
109
|
+
)
|
110
|
+
results.append(await self._shell(cmd, take_screenshot=False))
|
111
|
+
|
112
|
+
screenshot_base64 = await self._take_screenshot_after_delay()
|
113
|
+
return ToolResult(
|
114
|
+
output="".join(result.output or "" for result in results),
|
115
|
+
error="".join(result.error or "" for result in results),
|
116
|
+
base64_image=screenshot_base64,
|
117
|
+
)
|
118
|
+
|
119
|
+
async def cursor_position(self) -> ToolResult:
|
120
|
+
result = await self._shell(
|
121
|
+
f"{self.xdotool} getmouselocation --shell",
|
122
|
+
take_screenshot=False,
|
123
|
+
)
|
124
|
+
output = result.output or ""
|
125
|
+
x, y = self._scale_coordinates(
|
126
|
+
"computer",
|
127
|
+
int(output.split("X=")[1].split("\n")[0]),
|
128
|
+
int(output.split("Y=")[1].split("\n")[0]),
|
129
|
+
)
|
130
|
+
return result.replace(output=f"X={x},Y={y}")
|
131
|
+
|
132
|
+
async def left_mouse_down(self) -> ToolResult:
|
133
|
+
return await self._shell(f"{self.xdotool} mousedown 1")
|
134
|
+
|
135
|
+
async def left_mouse_up(self) -> ToolResult:
|
136
|
+
return await self._shell(f"{self.xdotool} mouseup 1")
|
137
|
+
|
138
|
+
async def mouse_move(self, coordinate: tuple[int, int]) -> ToolResult:
|
139
|
+
return await self._mouse_move_and("mouse_move", coordinate, None)
|
140
|
+
|
141
|
+
async def left_click(
|
142
|
+
self, coordinate: tuple[int, int] | None, text: str | None
|
143
|
+
) -> ToolResult:
|
144
|
+
return await self._mouse_move_and("left_click", coordinate, text)
|
145
|
+
|
146
|
+
async def right_click(
|
147
|
+
self, coordinate: tuple[int, int] | None, text: str | None
|
148
|
+
) -> ToolResult:
|
149
|
+
return await self._mouse_move_and("right_click", coordinate, text)
|
150
|
+
|
151
|
+
async def middle_click(
|
152
|
+
self, coordinate: tuple[int, int] | None, text: str | None
|
153
|
+
) -> ToolResult:
|
154
|
+
return await self._mouse_move_and("middle_click", coordinate, text)
|
155
|
+
|
156
|
+
async def double_click(
|
157
|
+
self, coordinate: tuple[int, int] | None, text: str | None
|
158
|
+
) -> ToolResult:
|
159
|
+
return await self._mouse_move_and("double_click", coordinate, text)
|
160
|
+
|
161
|
+
async def triple_click(
|
162
|
+
self, coordinate: tuple[int, int] | None, text: str | None
|
163
|
+
) -> ToolResult:
|
164
|
+
return await self._mouse_move_and("triple_click", coordinate, text)
|
165
|
+
|
166
|
+
async def left_click_drag(
|
167
|
+
self, start_coordinate: tuple[int, int], coordinate: tuple[int, int]
|
168
|
+
) -> ToolResult:
|
169
|
+
await self._move_mouse_to_coordinate(start_coordinate, False)
|
170
|
+
x, y = self._scale_coordinates("api", *coordinate)
|
171
|
+
return await self._shell(
|
172
|
+
f"{self.xdotool} mousedown 1 mousemove --sync {x} {y} mouseup 1"
|
173
|
+
)
|
174
|
+
|
175
|
+
async def scroll(
|
109
176
|
self,
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
177
|
+
scroll_direction: Literal["up", "down", "left", "right"],
|
178
|
+
scroll_amount: int,
|
179
|
+
coordinate: tuple[int, int] | None,
|
180
|
+
text: str | None,
|
181
|
+
) -> ToolResult:
|
182
|
+
if coordinate:
|
183
|
+
await self._move_mouse_to_coordinate(coordinate, False)
|
184
|
+
scroll_button = {
|
185
|
+
"up": 4,
|
186
|
+
"down": 5,
|
187
|
+
"left": 6,
|
188
|
+
"right": 7,
|
189
|
+
}[scroll_direction]
|
190
|
+
|
191
|
+
if text:
|
192
|
+
key_arg = _key_arg_for_text(text)
|
193
|
+
await self._shell(f"{self.xdotool} keydown -- {key_arg}", False)
|
194
|
+
await self._shell(
|
195
|
+
f"{self.xdotool} click --repeat {scroll_amount} {scroll_button}",
|
196
|
+
False,
|
197
|
+
)
|
198
|
+
return await self._shell(f"{self.xdotool} keyup -- {key_arg}")
|
199
|
+
else:
|
200
|
+
return await self._shell(
|
201
|
+
f"{self.xdotool} click --repeat {scroll_amount} {scroll_button}"
|
202
|
+
)
|
127
203
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
raise ToolError(f"coordinate is not accepted for {action}")
|
140
|
-
if not isinstance(text, str):
|
141
|
-
raise ToolError(f"{text} must be a string")
|
142
|
-
|
143
|
-
if action == "key":
|
144
|
-
return await self.shell(
|
145
|
-
f"{self.xdotool} key -- {' '.join(shlex.quote(part) for part in text.split())}"
|
146
|
-
)
|
147
|
-
elif action == "type":
|
148
|
-
results: list[ToolResult] = []
|
149
|
-
for chunk in chunks(text, TYPING_GROUP_SIZE):
|
150
|
-
cmd = f"{self.xdotool} type --delay {TYPING_DELAY_MS} -- {shlex.quote(chunk)}"
|
151
|
-
results.append(await self.shell(cmd, take_screenshot=False))
|
152
|
-
|
153
|
-
screenshot_base64 = await self.take_screenshot_after_delay()
|
154
|
-
return ToolResult(
|
155
|
-
output="".join(result.output or "" for result in results),
|
156
|
-
error="".join(result.error or "" for result in results),
|
157
|
-
base64_image=screenshot_base64,
|
158
|
-
)
|
159
|
-
|
160
|
-
if action in (
|
204
|
+
async def wait(self, duration: int) -> ToolResult:
|
205
|
+
await asyncio.sleep(duration)
|
206
|
+
return await self.screenshot()
|
207
|
+
|
208
|
+
async def screenshot(self) -> ToolResult:
|
209
|
+
return await self._screenshot()
|
210
|
+
|
211
|
+
async def _mouse_move_and(
|
212
|
+
self,
|
213
|
+
action: Literal[
|
214
|
+
"mouse_move",
|
161
215
|
"left_click",
|
162
216
|
"right_click",
|
163
|
-
"double_click",
|
164
217
|
"middle_click",
|
165
|
-
"
|
166
|
-
"
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
218
|
+
"double_click",
|
219
|
+
"triple_click",
|
220
|
+
],
|
221
|
+
coordinate: tuple[int, int] | None,
|
222
|
+
text: str | None,
|
223
|
+
):
|
224
|
+
should_move = action == "mouse_move" or coordinate
|
225
|
+
if should_move:
|
226
|
+
assert coordinate # coding/type safety error
|
227
|
+
move_result = await self._move_mouse_to_coordinate(
|
228
|
+
coordinate, action == "mouse_move"
|
229
|
+
)
|
230
|
+
if action == "mouse_move":
|
231
|
+
return move_result
|
232
|
+
click_arg = {
|
233
|
+
"left_click": "1",
|
234
|
+
"right_click": "3",
|
235
|
+
"middle_click": "2",
|
236
|
+
"double_click": "--repeat 2 --delay 300 1",
|
237
|
+
"triple_click": "--repeat 3 --delay 300 1",
|
238
|
+
}[action]
|
239
|
+
|
240
|
+
if text:
|
241
|
+
key_arg = _key_arg_for_text(text)
|
242
|
+
await self._shell(f"{self.xdotool} keydown -- {key_arg}", False)
|
243
|
+
await self._shell(f"{self.xdotool} click {click_arg}", False)
|
244
|
+
return await self._shell(f"{self.xdotool} keyup -- {key_arg}")
|
245
|
+
else:
|
246
|
+
return await self._shell(f"{self.xdotool} click {click_arg}")
|
247
|
+
|
248
|
+
async def _move_mouse_to_coordinate(
|
249
|
+
self, coordinate: tuple[int, int], take_screenshot: bool
|
250
|
+
):
|
251
|
+
x, y = self._scale_coordinates("api", *coordinate)
|
252
|
+
return await self._shell(
|
253
|
+
f"{self.xdotool} mousemove --sync {x} {y}", take_screenshot=take_screenshot
|
254
|
+
)
|
255
|
+
|
256
|
+
async def _screenshot(self):
|
199
257
|
"""Take a screenshot of the current screen and return the base64 encoded image."""
|
200
258
|
output_dir = Path(OUTPUT_DIR)
|
201
259
|
output_dir.mkdir(parents=True, exist_ok=True)
|
202
260
|
path = output_dir / f"screenshot_{uuid4().hex}.png"
|
203
261
|
|
204
|
-
result = await self.
|
262
|
+
result = await self._shell(
|
205
263
|
f"{self._display_prefix}scrot --silent -p {path}", take_screenshot=False
|
206
264
|
)
|
207
265
|
if self._scaling_enabled:
|
208
|
-
x, y = self.
|
266
|
+
x, y = self._scale_coordinates("computer", self.width, self.height)
|
209
267
|
convert_cmd = f"convert {path} -resize {x}x{y}!"
|
210
268
|
if self.color_count is not None:
|
211
269
|
convert_cmd += f" -colors {self.color_count}"
|
212
270
|
convert_cmd += f" {path}"
|
213
|
-
await self.
|
271
|
+
await self._shell(convert_cmd, take_screenshot=False)
|
214
272
|
|
215
273
|
if path.exists():
|
216
274
|
return result.replace(
|
217
275
|
base64_image=base64.b64encode(path.read_bytes()).decode()
|
218
276
|
)
|
219
|
-
raise
|
277
|
+
raise X11ClientError(f"Failed to take screenshot: {result.error}")
|
220
278
|
|
221
|
-
async def
|
279
|
+
async def _shell(self, command: str, take_screenshot=True) -> ToolResult:
|
222
280
|
"""Run a shell command and return the output, error, and optionally a screenshot."""
|
223
281
|
logging.debug(f"running shell command {command}")
|
224
282
|
_, stdout, stderr = await run(command)
|
@@ -226,17 +284,17 @@ class X11Client:
|
|
226
284
|
return ToolResult(
|
227
285
|
output=stdout,
|
228
286
|
error=stderr,
|
229
|
-
base64_image=(await self.
|
287
|
+
base64_image=(await self._take_screenshot_after_delay())
|
230
288
|
if take_screenshot
|
231
289
|
else None,
|
232
290
|
)
|
233
291
|
|
234
|
-
async def
|
292
|
+
async def _take_screenshot_after_delay(self) -> str:
|
235
293
|
# delay to let things settle before taking a screenshot
|
236
294
|
await asyncio.sleep(self._screenshot_delay)
|
237
|
-
return (await self.
|
295
|
+
return (await self._screenshot()).base64_image
|
238
296
|
|
239
|
-
def
|
297
|
+
def _scale_coordinates(self, source: ScalingSource, x: int, y: int):
|
240
298
|
"""Scale coordinates to a target maximum resolution."""
|
241
299
|
if not self._scaling_enabled:
|
242
300
|
return x, y
|
@@ -255,8 +313,12 @@ class X11Client:
|
|
255
313
|
y_scaling_factor = target_dimension["height"] / self.height
|
256
314
|
if source == "api":
|
257
315
|
if x > self.width or y > self.height:
|
258
|
-
raise
|
316
|
+
raise X11ClientError(f"Coordinates {x}, {y} are out of bounds")
|
259
317
|
# scale up
|
260
318
|
return round(x / x_scaling_factor), round(y / y_scaling_factor)
|
261
319
|
# scale down
|
262
320
|
return round(x * x_scaling_factor), round(y * y_scaling_factor)
|
321
|
+
|
322
|
+
|
323
|
+
def _key_arg_for_text(text: str) -> str:
|
324
|
+
return " ".join(shlex.quote(part) for part in text.split())
|
@@ -1,15 +1,24 @@
|
|
1
|
-
import argparse
|
2
1
|
import asyncio
|
3
2
|
import json
|
4
3
|
import logging
|
5
4
|
import os
|
6
5
|
import sys
|
7
6
|
import time
|
7
|
+
from argparse import Namespace
|
8
|
+
from typing import TypeVar
|
8
9
|
|
10
|
+
from _args import parse_arguments
|
11
|
+
from _constants import Action
|
9
12
|
from _logger import setup_logger
|
10
13
|
from _tool_result import ToolResult
|
11
14
|
from _x11_client import X11Client
|
12
15
|
|
16
|
+
|
17
|
+
class ComputerToolError(Exception):
|
18
|
+
def __init__(self, message):
|
19
|
+
self.message = message
|
20
|
+
|
21
|
+
|
13
22
|
# This is a bit sketchy. We really want to use relative imports here. Using absolute imports
|
14
23
|
# works at runtime, but it prevents intellisense from working. However, when this folder is
|
15
24
|
# copied to the container, by default relative imports won't work if this file is launched
|
@@ -44,29 +53,67 @@ def main():
|
|
44
53
|
sys.exit(1)
|
45
54
|
|
46
55
|
|
47
|
-
def
|
48
|
-
parser = argparse.ArgumentParser(description="Execute computer tool action")
|
49
|
-
parser.add_argument("--action", type=str, required=True, help="Action to perform")
|
50
|
-
parser.add_argument("--text", type=str, help="Optional text parameter")
|
51
|
-
parser.add_argument(
|
52
|
-
"--coordinate",
|
53
|
-
type=int,
|
54
|
-
nargs=2,
|
55
|
-
help="Optional coordinate parameter as a list of two integers",
|
56
|
-
)
|
57
|
-
return parser.parse_args()
|
58
|
-
|
59
|
-
|
60
|
-
async def execute_action(args) -> ToolResult:
|
56
|
+
async def execute_action(args: Namespace) -> ToolResult:
|
61
57
|
# we can't do anything until X11 is ready to go.
|
62
58
|
await wait_for_file("/tmp/xfce_started")
|
63
59
|
|
64
60
|
computer = X11Client()
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
61
|
+
action: Action = args.action
|
62
|
+
match action:
|
63
|
+
case "key":
|
64
|
+
return await computer.key(not_none(args.text, "text"))
|
65
|
+
case "hold_key":
|
66
|
+
return await computer.hold_key(
|
67
|
+
not_none(args.text, "text"), not_none(args.duration, "duration")
|
68
|
+
)
|
69
|
+
case "type":
|
70
|
+
return await computer.type(not_none(args.text, "text"))
|
71
|
+
case "cursor_position":
|
72
|
+
return await computer.cursor_position()
|
73
|
+
case "left_mouse_down":
|
74
|
+
return await computer.left_mouse_down()
|
75
|
+
case "left_mouse_up":
|
76
|
+
return await computer.left_mouse_up()
|
77
|
+
case "mouse_move":
|
78
|
+
return await computer.mouse_move(not_none(args.coordinate, "coordinate"))
|
79
|
+
case "left_click":
|
80
|
+
return await computer.left_click(
|
81
|
+
getattr(args, "coordinate", None), getattr(args, "text", None)
|
82
|
+
)
|
83
|
+
case "right_click":
|
84
|
+
return await computer.right_click(
|
85
|
+
getattr(args, "coordinate", None), getattr(args, "text", None)
|
86
|
+
)
|
87
|
+
case "middle_click":
|
88
|
+
return await computer.middle_click(
|
89
|
+
getattr(args, "coordinate", None), getattr(args, "text", None)
|
90
|
+
)
|
91
|
+
case "double_click":
|
92
|
+
return await computer.double_click(
|
93
|
+
getattr(args, "coordinate", None), getattr(args, "text", None)
|
94
|
+
)
|
95
|
+
case "triple_click":
|
96
|
+
return await computer.triple_click(
|
97
|
+
getattr(args, "coordinate", None), getattr(args, "text", None)
|
98
|
+
)
|
99
|
+
case "left_click_drag":
|
100
|
+
return await computer.left_click_drag(
|
101
|
+
not_none(args.start_coordinate, "start_coordinate"),
|
102
|
+
not_none(args.coordinate, "coordinate"),
|
103
|
+
)
|
104
|
+
case "scroll":
|
105
|
+
return await computer.scroll(
|
106
|
+
not_none(args.scroll_direction, "scroll_direction"),
|
107
|
+
not_none(args.scroll_amount, "scroll_amount"),
|
108
|
+
getattr(args, "coordinate", None),
|
109
|
+
getattr(args, "text", None),
|
110
|
+
)
|
111
|
+
case "wait":
|
112
|
+
return await computer.wait(not_none(args.duration, "duration"))
|
113
|
+
case "screenshot":
|
114
|
+
return await computer.screenshot()
|
115
|
+
|
116
|
+
raise ComputerToolError(f"Invalid action: {action}")
|
70
117
|
|
71
118
|
|
72
119
|
async def wait_for_file(file_path, check_interval=1):
|
@@ -81,5 +128,14 @@ async def wait_for_file(file_path, check_interval=1):
|
|
81
128
|
)
|
82
129
|
|
83
130
|
|
131
|
+
T = TypeVar("T")
|
132
|
+
|
133
|
+
|
134
|
+
def not_none(value: T | None, name: str) -> T:
|
135
|
+
if value is None:
|
136
|
+
raise ComputerToolError(f"{name} must be provided")
|
137
|
+
return value
|
138
|
+
|
139
|
+
|
84
140
|
if __name__ == "__main__":
|
85
141
|
main()
|
@@ -0,0 +1,65 @@
|
|
1
|
+
[build-system]
|
2
|
+
requires = ["setuptools>=64", "setuptools_scm[toml]>=8"]
|
3
|
+
build-backend = "setuptools.build_meta"
|
4
|
+
|
5
|
+
[tool.setuptools_scm]
|
6
|
+
|
7
|
+
[tool.setuptools.packages.find]
|
8
|
+
where = ["."]
|
9
|
+
include = ["inspect_ai*"]
|
10
|
+
|
11
|
+
[tool.ruff]
|
12
|
+
src = ["."]
|
13
|
+
|
14
|
+
[tool.ruff.lint]
|
15
|
+
select = [
|
16
|
+
"E", # pycodestyle errors
|
17
|
+
"W", # pycodestyle warnings
|
18
|
+
"F", # flake8
|
19
|
+
"D", # pydocstyle
|
20
|
+
"I", # isort
|
21
|
+
"SIM101", # duplicate isinstance
|
22
|
+
"UP038", # non-pep604-isinstance
|
23
|
+
# "RET", # flake8-return
|
24
|
+
# "RUF", # ruff rules
|
25
|
+
]
|
26
|
+
ignore = ["E203", "E501", "D10", "D212", "D415"]
|
27
|
+
|
28
|
+
[tool.ruff.lint.pydocstyle]
|
29
|
+
convention = "google"
|
30
|
+
|
31
|
+
[tool.pytest.ini_options]
|
32
|
+
minversion = "7.0"
|
33
|
+
addopts = "-rA --doctest-modules --color=yes"
|
34
|
+
doctest_optionflags = ["NORMALIZE_WHITESPACE", "IGNORE_EXCEPTION_DETAIL"]
|
35
|
+
asyncio_mode = "auto"
|
36
|
+
asyncio_default_fixture_loop_scope = "function"
|
37
|
+
log_level = "warning"
|
38
|
+
|
39
|
+
[tool.mypy]
|
40
|
+
warn_unused_ignores = true
|
41
|
+
no_implicit_reexport = true
|
42
|
+
strict_equality = true
|
43
|
+
warn_redundant_casts = true
|
44
|
+
warn_unused_configs = true
|
45
|
+
disallow_any_explicit = true
|
46
|
+
disallow_any_generics = true
|
47
|
+
disallow_subclassing_any = true
|
48
|
+
plugins=["pydantic.mypy"]
|
49
|
+
|
50
|
+
|
51
|
+
[tool.pydantic-mypy]
|
52
|
+
init_forbid_extra = true
|
53
|
+
init_typed = true
|
54
|
+
|
55
|
+
[tool.check-wheel-contents]
|
56
|
+
ignore = ["W002", "W009"]
|
57
|
+
|
58
|
+
[project]
|
59
|
+
name = "web_browser_tool_container"
|
60
|
+
requires-python = ">=3.10"
|
61
|
+
dynamic = ["version", "dependencies"]
|
62
|
+
|
63
|
+
|
64
|
+
[project.optional-dependencies]
|
65
|
+
dev = ["pytest"]
|