inspect-ai 0.3.70__py3-none-any.whl → 0.3.72__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 -8
- inspect_ai/_display/core/display.py +2 -0
- inspect_ai/_display/core/footer.py +13 -3
- inspect_ai/_display/plain/display.py +6 -2
- inspect_ai/_display/rich/display.py +19 -6
- inspect_ai/_display/textual/app.py +6 -1
- inspect_ai/_display/textual/display.py +4 -0
- inspect_ai/_display/textual/widgets/transcript.py +10 -6
- inspect_ai/_eval/task/run.py +5 -8
- inspect_ai/_util/content.py +20 -1
- inspect_ai/_util/transcript.py +10 -4
- inspect_ai/_util/working.py +4 -0
- inspect_ai/_view/www/App.css +6 -0
- inspect_ai/_view/www/dist/assets/index.css +115 -87
- inspect_ai/_view/www/dist/assets/index.js +5324 -2276
- inspect_ai/_view/www/eslint.config.mjs +24 -1
- inspect_ai/_view/www/log-schema.json +283 -20
- inspect_ai/_view/www/package.json +8 -3
- inspect_ai/_view/www/src/App.tsx +2 -2
- inspect_ai/_view/www/src/components/AnsiDisplay.tsx +4 -3
- inspect_ai/_view/www/src/components/Card.tsx +9 -8
- inspect_ai/_view/www/src/components/DownloadButton.tsx +2 -1
- inspect_ai/_view/www/src/components/EmptyPanel.tsx +2 -2
- inspect_ai/_view/www/src/components/ErrorPanel.tsx +4 -3
- inspect_ai/_view/www/src/components/ExpandablePanel.tsx +13 -5
- inspect_ai/_view/www/src/components/FindBand.tsx +3 -3
- inspect_ai/_view/www/src/components/HumanBaselineView.tsx +3 -3
- inspect_ai/_view/www/src/components/LabeledValue.tsx +5 -4
- inspect_ai/_view/www/src/components/LargeModal.tsx +18 -13
- inspect_ai/_view/www/src/components/{LightboxCarousel.css → LightboxCarousel.module.css} +22 -18
- inspect_ai/_view/www/src/components/LightboxCarousel.tsx +36 -27
- inspect_ai/_view/www/src/components/MessageBand.tsx +2 -1
- inspect_ai/_view/www/src/components/NavPills.tsx +9 -8
- inspect_ai/_view/www/src/components/ProgressBar.tsx +2 -1
- inspect_ai/_view/www/src/components/TabSet.tsx +21 -15
- inspect_ai/_view/www/src/index.tsx +2 -2
- inspect_ai/_view/www/src/metadata/MetaDataGrid.tsx +11 -9
- inspect_ai/_view/www/src/metadata/MetaDataView.tsx +3 -2
- inspect_ai/_view/www/src/metadata/MetadataGrid.module.css +1 -0
- inspect_ai/_view/www/src/metadata/RenderedContent.tsx +16 -0
- inspect_ai/_view/www/src/plan/DatasetDetailView.tsx +3 -2
- inspect_ai/_view/www/src/plan/DetailStep.tsx +2 -1
- inspect_ai/_view/www/src/plan/PlanCard.tsx +2 -5
- inspect_ai/_view/www/src/plan/PlanDetailView.tsx +6 -9
- inspect_ai/_view/www/src/plan/ScorerDetailView.tsx +2 -1
- inspect_ai/_view/www/src/plan/SolverDetailView.tsx +3 -3
- inspect_ai/_view/www/src/samples/InlineSampleDisplay.tsx +2 -2
- inspect_ai/_view/www/src/samples/SampleDialog.tsx +3 -3
- inspect_ai/_view/www/src/samples/SampleDisplay.tsx +2 -2
- inspect_ai/_view/www/src/samples/SampleSummaryView.tsx +2 -2
- inspect_ai/_view/www/src/samples/SamplesTools.tsx +2 -1
- inspect_ai/_view/www/src/samples/chat/ChatMessage.tsx +3 -19
- inspect_ai/_view/www/src/samples/chat/ChatMessageRenderer.tsx +2 -1
- inspect_ai/_view/www/src/samples/chat/ChatMessageRow.tsx +2 -1
- inspect_ai/_view/www/src/samples/chat/ChatView.tsx +2 -1
- inspect_ai/_view/www/src/samples/chat/ChatViewVirtualList.tsx +22 -7
- inspect_ai/_view/www/src/samples/chat/MessageContent.tsx +35 -6
- inspect_ai/_view/www/src/samples/chat/MessageContents.tsx +2 -2
- inspect_ai/_view/www/src/samples/chat/messages.ts +15 -2
- inspect_ai/_view/www/src/samples/chat/tools/ToolCallView.tsx +13 -4
- inspect_ai/_view/www/src/samples/chat/tools/ToolInput.module.css +2 -2
- inspect_ai/_view/www/src/samples/chat/tools/ToolInput.tsx +18 -19
- inspect_ai/_view/www/src/samples/chat/tools/ToolOutput.module.css +1 -1
- inspect_ai/_view/www/src/samples/chat/tools/ToolOutput.tsx +4 -3
- inspect_ai/_view/www/src/samples/chat/tools/ToolTitle.tsx +2 -2
- inspect_ai/_view/www/src/samples/error/FlatSampleErrorView.tsx +2 -3
- inspect_ai/_view/www/src/samples/error/SampleErrorView.tsx +3 -2
- inspect_ai/_view/www/src/samples/list/SampleFooter.tsx +2 -1
- inspect_ai/_view/www/src/samples/list/SampleHeader.tsx +2 -1
- inspect_ai/_view/www/src/samples/list/SampleList.tsx +57 -45
- inspect_ai/_view/www/src/samples/list/SampleRow.tsx +2 -1
- inspect_ai/_view/www/src/samples/list/SampleSeparator.tsx +2 -1
- inspect_ai/_view/www/src/samples/sample-tools/EpochFilter.tsx +2 -2
- inspect_ai/_view/www/src/samples/sample-tools/SelectScorer.tsx +4 -3
- inspect_ai/_view/www/src/samples/sample-tools/SortFilter.tsx +2 -5
- inspect_ai/_view/www/src/samples/sample-tools/sample-filter/SampleFilter.tsx +2 -2
- inspect_ai/_view/www/src/samples/scores/SampleScoreView.tsx +2 -1
- inspect_ai/_view/www/src/samples/scores/SampleScores.tsx +2 -2
- inspect_ai/_view/www/src/samples/transcript/ApprovalEventView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/ErrorEventView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/InfoEventView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/InputEventView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/LoggerEventView.module.css +4 -0
- inspect_ai/_view/www/src/samples/transcript/LoggerEventView.tsx +12 -2
- inspect_ai/_view/www/src/samples/transcript/ModelEventView.module.css +1 -1
- inspect_ai/_view/www/src/samples/transcript/ModelEventView.tsx +25 -28
- inspect_ai/_view/www/src/samples/transcript/SampleInitEventView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/SampleLimitEventView.tsx +5 -4
- inspect_ai/_view/www/src/samples/transcript/SampleTranscript.tsx +2 -2
- inspect_ai/_view/www/src/samples/transcript/SandboxEventView.tsx +8 -7
- inspect_ai/_view/www/src/samples/transcript/ScoreEventView.tsx +2 -2
- inspect_ai/_view/www/src/samples/transcript/StepEventView.tsx +3 -3
- inspect_ai/_view/www/src/samples/transcript/SubtaskEventView.tsx +18 -14
- inspect_ai/_view/www/src/samples/transcript/ToolEventView.tsx +5 -5
- inspect_ai/_view/www/src/samples/transcript/TranscriptView.tsx +34 -15
- inspect_ai/_view/www/src/samples/transcript/event/EventNav.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/event/EventNavs.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/event/EventRow.tsx +3 -2
- inspect_ai/_view/www/src/samples/transcript/event/EventSection.tsx +2 -2
- inspect_ai/_view/www/src/samples/transcript/event/EventTimingPanel.module.css +28 -0
- inspect_ai/_view/www/src/samples/transcript/event/EventTimingPanel.tsx +115 -0
- inspect_ai/_view/www/src/samples/transcript/event/utils.ts +29 -0
- inspect_ai/_view/www/src/samples/transcript/state/StateDiffView.tsx +2 -1
- inspect_ai/_view/www/src/samples/transcript/state/StateEventRenderers.tsx +3 -3
- inspect_ai/_view/www/src/samples/transcript/state/StateEventView.tsx +11 -8
- inspect_ai/_view/www/src/types/log.d.ts +129 -34
- inspect_ai/_view/www/src/usage/ModelTokenTable.tsx +6 -10
- inspect_ai/_view/www/src/usage/ModelUsagePanel.module.css +4 -0
- inspect_ai/_view/www/src/usage/ModelUsagePanel.tsx +32 -9
- inspect_ai/_view/www/src/usage/TokenTable.tsx +4 -6
- inspect_ai/_view/www/src/usage/UsageCard.tsx +2 -1
- inspect_ai/_view/www/src/utils/format.ts +1 -1
- inspect_ai/_view/www/src/utils/json.ts +24 -0
- inspect_ai/_view/www/src/workspace/WorkSpace.tsx +6 -5
- inspect_ai/_view/www/src/workspace/WorkSpaceView.tsx +9 -2
- inspect_ai/_view/www/src/workspace/error/TaskErrorPanel.tsx +2 -1
- inspect_ai/_view/www/src/workspace/navbar/Navbar.tsx +2 -1
- inspect_ai/_view/www/src/workspace/navbar/PrimaryBar.tsx +3 -3
- inspect_ai/_view/www/src/workspace/navbar/ResultsPanel.tsx +4 -3
- inspect_ai/_view/www/src/workspace/navbar/SecondaryBar.tsx +5 -4
- inspect_ai/_view/www/src/workspace/navbar/StatusPanel.tsx +5 -8
- inspect_ai/_view/www/src/workspace/sidebar/EvalStatus.tsx +5 -4
- inspect_ai/_view/www/src/workspace/sidebar/LogDirectoryTitleView.tsx +2 -1
- inspect_ai/_view/www/src/workspace/sidebar/Sidebar.tsx +2 -1
- inspect_ai/_view/www/src/workspace/sidebar/SidebarLogEntry.tsx +2 -2
- inspect_ai/_view/www/src/workspace/sidebar/SidebarScoreView.tsx +2 -1
- inspect_ai/_view/www/src/workspace/sidebar/SidebarScoresView.tsx +2 -2
- inspect_ai/_view/www/src/workspace/tabs/InfoTab.tsx +2 -2
- inspect_ai/_view/www/src/workspace/tabs/JsonTab.tsx +2 -5
- inspect_ai/_view/www/src/workspace/tabs/SamplesTab.tsx +12 -11
- inspect_ai/_view/www/yarn.lock +241 -5
- inspect_ai/log/_condense.py +3 -0
- inspect_ai/log/_recorders/eval.py +6 -1
- inspect_ai/log/_transcript.py +58 -1
- inspect_ai/model/__init__.py +2 -0
- inspect_ai/model/_call_tools.py +7 -0
- inspect_ai/model/_chat_message.py +22 -7
- inspect_ai/model/_conversation.py +10 -8
- inspect_ai/model/_generate_config.py +25 -4
- inspect_ai/model/_model.py +133 -57
- inspect_ai/model/_model_output.py +3 -0
- inspect_ai/model/_openai.py +106 -40
- inspect_ai/model/_providers/anthropic.py +281 -153
- inspect_ai/model/_providers/google.py +27 -8
- inspect_ai/model/_providers/groq.py +9 -4
- inspect_ai/model/_providers/openai.py +57 -4
- inspect_ai/model/_providers/openai_o1.py +10 -0
- inspect_ai/model/_providers/providers.py +1 -1
- inspect_ai/model/_reasoning.py +15 -2
- inspect_ai/scorer/_model.py +23 -19
- inspect_ai/solver/_human_agent/agent.py +14 -10
- inspect_ai/solver/_human_agent/commands/__init__.py +7 -3
- inspect_ai/solver/_human_agent/commands/submit.py +76 -30
- inspect_ai/tool/__init__.py +2 -0
- inspect_ai/tool/_tool.py +3 -1
- 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/_run.py +1 -1
- 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_browser/_resources/.pylintrc +8 -0
- inspect_ai/tool/_tools/_web_browser/_resources/.vscode/launch.json +24 -0
- inspect_ai/tool/_tools/_web_browser/_resources/.vscode/settings.json +25 -0
- inspect_ai/tool/_tools/_web_browser/_resources/Dockerfile +5 -6
- inspect_ai/tool/_tools/_web_browser/_resources/README.md +10 -11
- inspect_ai/tool/_tools/_web_browser/_resources/accessibility_tree.py +71 -0
- inspect_ai/tool/_tools/_web_browser/_resources/accessibility_tree_node.py +323 -0
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/__init__.py +5 -0
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/a11y.py +279 -0
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/dom.py +9 -0
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/dom_snapshot.py +293 -0
- inspect_ai/tool/_tools/_web_browser/_resources/cdp/page.py +94 -0
- inspect_ai/tool/_tools/_web_browser/_resources/constants.py +2 -0
- inspect_ai/tool/_tools/_web_browser/_resources/images/usage_diagram.svg +2 -0
- inspect_ai/tool/_tools/_web_browser/_resources/playwright_browser.py +50 -0
- inspect_ai/tool/_tools/_web_browser/_resources/playwright_crawler.py +31 -359
- inspect_ai/tool/_tools/_web_browser/_resources/playwright_page_crawler.py +280 -0
- inspect_ai/tool/_tools/_web_browser/_resources/pyproject.toml +65 -0
- inspect_ai/tool/_tools/_web_browser/_resources/rectangle.py +64 -0
- inspect_ai/tool/_tools/_web_browser/_resources/rpc_client_helpers.py +146 -0
- inspect_ai/tool/_tools/_web_browser/_resources/scale_factor.py +64 -0
- inspect_ai/tool/_tools/_web_browser/_resources/test_accessibility_tree_node.py +180 -0
- inspect_ai/tool/_tools/_web_browser/_resources/test_playwright_crawler.py +15 -9
- inspect_ai/tool/_tools/_web_browser/_resources/test_rectangle.py +15 -0
- inspect_ai/tool/_tools/_web_browser/_resources/test_web_client.py +44 -0
- inspect_ai/tool/_tools/_web_browser/_resources/web_browser_rpc_types.py +39 -0
- inspect_ai/tool/_tools/_web_browser/_resources/web_client.py +198 -48
- inspect_ai/tool/_tools/_web_browser/_resources/web_client_new_session.py +26 -25
- inspect_ai/tool/_tools/_web_browser/_resources/web_server.py +178 -39
- inspect_ai/tool/_tools/_web_browser/_web_browser.py +38 -19
- inspect_ai/util/__init__.py +2 -1
- inspect_ai/util/_display.py +12 -0
- inspect_ai/util/_sandbox/events.py +55 -21
- inspect_ai/util/_sandbox/self_check.py +131 -43
- inspect_ai/util/_subtask.py +11 -0
- {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.72.dist-info}/METADATA +1 -1
- {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.72.dist-info}/RECORD +209 -186
- {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.72.dist-info}/WHEEL +1 -1
- inspect_ai/_view/www/src/components/VirtualList.module.css +0 -19
- inspect_ai/_view/www/src/components/VirtualList.tsx +0 -292
- inspect_ai/tool/_tools/_computer/_computer_split.py +0 -198
- inspect_ai/tool/_tools/_web_browser/_resources/accessibility_node.py +0 -312
- inspect_ai/tool/_tools/_web_browser/_resources/dm_env_servicer.py +0 -275
- inspect_ai/tool/_tools/_web_browser/_resources/images/usage_diagram.png +0 -0
- inspect_ai/tool/_tools/_web_browser/_resources/test_accessibility_node.py +0 -176
- inspect_ai/tool/_tools/_web_browser/_resources/test_dm_env_servicer.py +0 -135
- inspect_ai/tool/_tools/_web_browser/_resources/test_web_environment.py +0 -71
- inspect_ai/tool/_tools/_web_browser/_resources/web_environment.py +0 -184
- {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.72.dist-info}/LICENSE +0 -0
- {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.72.dist-info}/entry_points.txt +0 -0
- {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.72.dist-info}/top_level.txt +0 -0
@@ -1,312 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from typing import Any
|
4
|
-
|
5
|
-
# Properities to ignore when printing out the accessibility tree.
|
6
|
-
_IGNORED_ACTREE_PROPERTIES = (
|
7
|
-
"focusable",
|
8
|
-
"readonly",
|
9
|
-
"level",
|
10
|
-
"settable",
|
11
|
-
"multiline",
|
12
|
-
"invalid",
|
13
|
-
)
|
14
|
-
|
15
|
-
_IGNORED_AT_ROLES = (
|
16
|
-
"generic",
|
17
|
-
"list",
|
18
|
-
"strong",
|
19
|
-
"paragraph",
|
20
|
-
"banner",
|
21
|
-
"navigation",
|
22
|
-
"Section",
|
23
|
-
"LabelText",
|
24
|
-
"Legend",
|
25
|
-
"listitem",
|
26
|
-
)
|
27
|
-
|
28
|
-
# Typically we use the bounding rect provided by the DOM in order to track
|
29
|
-
# if an element is visible or not. However, sometimes it can be useful to
|
30
|
-
# instead take the union of the elements bounds and all its children's bounds.
|
31
|
-
# This process is a little slower, and is probably not needed, but can be
|
32
|
-
# enabled using this flag.
|
33
|
-
USE_UNION_BOUNDS = False
|
34
|
-
|
35
|
-
# The maximum length of the description 'tag' for each element.
|
36
|
-
# Tags longer than this will be truncated with an '...' appended.
|
37
|
-
MAX_NODE_TEXT_LENGTH = 120
|
38
|
-
|
39
|
-
# Used for debugging, include the elements bounding rect during output.
|
40
|
-
_INCLUDE_BOUNDS_IN_OUTPUT = False
|
41
|
-
|
42
|
-
|
43
|
-
class NodeBounds:
|
44
|
-
"""Class to hold bounding rect for notes."""
|
45
|
-
|
46
|
-
def __init__(self, x: int, y: int, width: int, height: int):
|
47
|
-
self.x = int(x)
|
48
|
-
self.y = int(y)
|
49
|
-
self.width = int(width)
|
50
|
-
self.height = int(height)
|
51
|
-
|
52
|
-
@classmethod
|
53
|
-
def from_lrtb(cls, left: int, right: int, top: int, bottom: int) -> NodeBounds:
|
54
|
-
return cls(left, top, right - left, bottom - top)
|
55
|
-
|
56
|
-
@property
|
57
|
-
def left(self) -> int:
|
58
|
-
return self.x
|
59
|
-
|
60
|
-
@property
|
61
|
-
def top(self) -> int:
|
62
|
-
return self.y
|
63
|
-
|
64
|
-
@property
|
65
|
-
def right(self) -> int:
|
66
|
-
return self.x + self.width
|
67
|
-
|
68
|
-
@property
|
69
|
-
def bottom(self) -> int:
|
70
|
-
return self.y + self.height
|
71
|
-
|
72
|
-
@property
|
73
|
-
def center_x(self) -> int:
|
74
|
-
return self.x + self.width // 2
|
75
|
-
|
76
|
-
@property
|
77
|
-
def center_y(self) -> int:
|
78
|
-
return self.y + self.height // 2
|
79
|
-
|
80
|
-
@property
|
81
|
-
def area(self) -> int:
|
82
|
-
return self.width * self.height
|
83
|
-
|
84
|
-
def __str__(self):
|
85
|
-
return f"({self.left}, {self.top}, {self.width}, {self.height})"
|
86
|
-
|
87
|
-
def union(self, other: NodeBounds) -> NodeBounds:
|
88
|
-
"""Return the (convex) union of two bounds."""
|
89
|
-
# Special cases when one or the other is an empty bound.
|
90
|
-
if self.area == 0:
|
91
|
-
return other
|
92
|
-
if other.area == 0:
|
93
|
-
return self
|
94
|
-
|
95
|
-
return self.from_lrtb(
|
96
|
-
min(self.left, other.left),
|
97
|
-
max(self.right, other.right),
|
98
|
-
min(self.top, other.top),
|
99
|
-
max(self.bottom, other.bottom),
|
100
|
-
)
|
101
|
-
|
102
|
-
def is_inside(self, x: int, y: int) -> bool:
|
103
|
-
"""Returns if given point is inside the bounds or not."""
|
104
|
-
return x >= self.left and y >= self.top and x < self.right and y < self.bottom
|
105
|
-
|
106
|
-
def overlaps(self, other: NodeBounds) -> bool:
|
107
|
-
"""Returns if the two bounds intersect."""
|
108
|
-
return (
|
109
|
-
other.left < self.right
|
110
|
-
and other.right > self.left
|
111
|
-
and other.top < self.bottom
|
112
|
-
and other.bottom > self.top
|
113
|
-
)
|
114
|
-
|
115
|
-
|
116
|
-
class AccessibilityNode:
|
117
|
-
"""A class for an accessibility node.
|
118
|
-
|
119
|
-
Accessibility properties are specified here:
|
120
|
-
https://chromedevtools.github.io/devtools-protocol/tot/Accessibility/#type-AXNode
|
121
|
-
"""
|
122
|
-
|
123
|
-
def __init__(self, node: dict[str, Any], parent=None, bounds=None):
|
124
|
-
self._node = node
|
125
|
-
self._parent: AccessibilityNode | None = parent
|
126
|
-
self.bounds = bounds or NodeBounds(0, 0, 0, 0)
|
127
|
-
|
128
|
-
def __getitem__(self, key: str) -> Any:
|
129
|
-
"""Access the underlying node fields."""
|
130
|
-
return self._node.get(key)
|
131
|
-
|
132
|
-
def __setitem__(self, key: str, value: Any):
|
133
|
-
"""Set the underlying node fields."""
|
134
|
-
self._node[key] = value
|
135
|
-
|
136
|
-
def __str__(self):
|
137
|
-
if _INCLUDE_BOUNDS_IN_OUTPUT:
|
138
|
-
bounds_string = " " + str(
|
139
|
-
self.get_union_bounds() if USE_UNION_BOUNDS else self.bounds
|
140
|
-
)
|
141
|
-
else:
|
142
|
-
bounds_string = ""
|
143
|
-
|
144
|
-
node_input = self.current_input
|
145
|
-
if node_input:
|
146
|
-
input_string = f' Current input: "{node_input}"'
|
147
|
-
else:
|
148
|
-
input_string = ""
|
149
|
-
|
150
|
-
# Without bounds we can't click on the element, and therefore the ID
|
151
|
-
# can not be used. Still useful to show these elements though (e.g.)
|
152
|
-
# to let user know about options.
|
153
|
-
id_string = "*" if self.bounds.area == 0 else self.node_id
|
154
|
-
|
155
|
-
url_string = ""
|
156
|
-
if self.role == "image" and self._node.get("src", ""):
|
157
|
-
url_string = " " + self._node["src"]
|
158
|
-
|
159
|
-
return (
|
160
|
-
f'[{id_string}] {self.role} "{self.short_name}"'
|
161
|
-
+ url_string
|
162
|
-
+ input_string
|
163
|
-
+ self.property_string()
|
164
|
-
+ bounds_string
|
165
|
-
)
|
166
|
-
|
167
|
-
@property
|
168
|
-
def node_id(self) -> str:
|
169
|
-
return self._node["nodeId"]
|
170
|
-
|
171
|
-
@property
|
172
|
-
def value(self) -> str:
|
173
|
-
value_field = self._node.get("value", "{}")
|
174
|
-
if isinstance(value_field, dict):
|
175
|
-
return value_field.get("value", "")
|
176
|
-
else:
|
177
|
-
return ""
|
178
|
-
|
179
|
-
@property
|
180
|
-
def parent(self) -> Any | None:
|
181
|
-
"""Returns the first visible parent."""
|
182
|
-
parent = self._parent
|
183
|
-
while parent and parent.is_ignored:
|
184
|
-
parent = parent._parent
|
185
|
-
return parent
|
186
|
-
|
187
|
-
@property
|
188
|
-
def role(self) -> str:
|
189
|
-
if "role" in self._node:
|
190
|
-
return self._node["role"].get("value", "")
|
191
|
-
else:
|
192
|
-
return ""
|
193
|
-
|
194
|
-
@property
|
195
|
-
def name(self) -> str:
|
196
|
-
if "name" in self._node:
|
197
|
-
return self._node["name"].get("value", "")
|
198
|
-
else:
|
199
|
-
return ""
|
200
|
-
|
201
|
-
@property
|
202
|
-
def short_name(self) -> str:
|
203
|
-
short_name = self.name[:MAX_NODE_TEXT_LENGTH]
|
204
|
-
if short_name != self.name:
|
205
|
-
short_name = short_name[: MAX_NODE_TEXT_LENGTH - 3] + "..."
|
206
|
-
return short_name
|
207
|
-
|
208
|
-
@property
|
209
|
-
def is_ignored(self) -> bool:
|
210
|
-
return (
|
211
|
-
self._node["ignored"]
|
212
|
-
or self.role in _IGNORED_AT_ROLES
|
213
|
-
or not self.is_visible
|
214
|
-
or (not self.name and self.role != "textbox")
|
215
|
-
or (self.parent and self.parent.name == self.name)
|
216
|
-
)
|
217
|
-
|
218
|
-
@property
|
219
|
-
def children(self) -> list[Any]:
|
220
|
-
return self._node.get("children", [])
|
221
|
-
|
222
|
-
@property
|
223
|
-
def is_visible(self) -> bool:
|
224
|
-
return self._node.get("is_visible", True)
|
225
|
-
|
226
|
-
def is_selected(self) -> bool:
|
227
|
-
return self._node.get("selected", False)
|
228
|
-
|
229
|
-
@property
|
230
|
-
def is_editable(self) -> bool:
|
231
|
-
for node_property in self.properties:
|
232
|
-
if node_property["name"] == "editable":
|
233
|
-
return True
|
234
|
-
return False
|
235
|
-
|
236
|
-
@property
|
237
|
-
def is_expanded(self) -> bool:
|
238
|
-
for node_property in self.properties:
|
239
|
-
if node_property["name"] == "expanded":
|
240
|
-
return bool(node_property["value"].get("value"))
|
241
|
-
return False
|
242
|
-
|
243
|
-
@property
|
244
|
-
def properties(self) -> list[Any]:
|
245
|
-
return self._node.get("properties", [])
|
246
|
-
|
247
|
-
@property
|
248
|
-
def dom_id(self) -> str:
|
249
|
-
"""Returns the backend DOM id associated with this node."""
|
250
|
-
return self._node.get("backendDOMNodeId", -1)
|
251
|
-
|
252
|
-
@property
|
253
|
-
def current_input(self) -> str:
|
254
|
-
# For some reason text edit boxes get 'input' but comboboxs get 'values'
|
255
|
-
if self.role == "combobox":
|
256
|
-
value = self.value
|
257
|
-
else:
|
258
|
-
value = self.get_property("input")
|
259
|
-
return value
|
260
|
-
|
261
|
-
def get_union_bounds(self) -> list[int]:
|
262
|
-
"""Returns the union of the bounds for this element all children."""
|
263
|
-
bounds = self.bounds
|
264
|
-
for child in self.children:
|
265
|
-
child_union_bounds = child.get_union_bounds()
|
266
|
-
bounds = bounds.union(child_union_bounds)
|
267
|
-
return bounds
|
268
|
-
|
269
|
-
def link_children(self, node_lookup: dict[str, Any]) -> None:
|
270
|
-
"""Creates links to children nodes.
|
271
|
-
|
272
|
-
Nodes are referenced by id, but we want to add references to the
|
273
|
-
AccessibilityNode. We also want add a reference to the parent node.
|
274
|
-
|
275
|
-
To perform this task we need access to the complete list of nodes, and
|
276
|
-
therefore it must be done on a second pass after initializing the nodes.
|
277
|
-
|
278
|
-
Args:
|
279
|
-
node_lookup: A dictionary mapping from node_id to node instances.
|
280
|
-
"""
|
281
|
-
self._node["children"] = []
|
282
|
-
for idx in self._node.get("childIds", []):
|
283
|
-
self._node["children"].append(node_lookup[idx])
|
284
|
-
node_lookup[idx]._parent = self
|
285
|
-
|
286
|
-
def get_property(self, property_name: str) -> Any:
|
287
|
-
"""Query the property value of the underlying accessibility tree node."""
|
288
|
-
return self._node.get(property_name)
|
289
|
-
|
290
|
-
def to_string(self, indent: int = 0, include_children: bool = True) -> str:
|
291
|
-
"""Returns the string representation of the node."""
|
292
|
-
result = ""
|
293
|
-
if not self.is_ignored:
|
294
|
-
result = " " * indent + str(self) + "\n"
|
295
|
-
indent += 1
|
296
|
-
if include_children:
|
297
|
-
children_strings = [child.to_string(indent) for child in self.children]
|
298
|
-
result = result + "".join([s for s in children_strings if s])
|
299
|
-
return result
|
300
|
-
|
301
|
-
def property_string(self) -> str:
|
302
|
-
properties = []
|
303
|
-
for node_property in self.properties:
|
304
|
-
try:
|
305
|
-
if node_property["name"] in _IGNORED_ACTREE_PROPERTIES:
|
306
|
-
continue
|
307
|
-
properties.append(
|
308
|
-
f"{node_property['name']}: {node_property['value'].get('value')}"
|
309
|
-
)
|
310
|
-
except KeyError:
|
311
|
-
pass
|
312
|
-
return " [" + ", ".join(properties) + "]" if properties else ""
|
@@ -1,275 +0,0 @@
|
|
1
|
-
"""Environment service that allows clients to run shell commands in steps."""
|
2
|
-
|
3
|
-
import threading
|
4
|
-
from typing import Any, Iterable, Type
|
5
|
-
|
6
|
-
import dm_env
|
7
|
-
import grpc
|
8
|
-
import playwright_crawler
|
9
|
-
from dm_env import specs
|
10
|
-
from dm_env_rpc.v1 import (
|
11
|
-
dm_env_rpc_pb2,
|
12
|
-
dm_env_rpc_pb2_grpc,
|
13
|
-
dm_env_utils,
|
14
|
-
spec_manager,
|
15
|
-
)
|
16
|
-
from google.rpc import code_pb2, status_pb2
|
17
|
-
|
18
|
-
_DEFAULT_WORLD_NAME = "WebBrowser"
|
19
|
-
|
20
|
-
|
21
|
-
class EnvironmentSpec:
|
22
|
-
"""Specifications for a dm_environment.
|
23
|
-
|
24
|
-
This class holds action and observation specs, as well as the required
|
25
|
-
managers to pack actions and observations.
|
26
|
-
"""
|
27
|
-
|
28
|
-
def __init__(self, env: dm_env.Environment):
|
29
|
-
convert = dm_env_utils.dm_env_spec_to_tensor_spec
|
30
|
-
|
31
|
-
# We support either a single spec, of flat dictionary of specs.
|
32
|
-
# In the dictionary case we need to map names to unique IDs.
|
33
|
-
env_obs_spec: dict[str, Any] = env.observation_spec()
|
34
|
-
if isinstance(env_obs_spec, specs.Array):
|
35
|
-
self.observation_spec = {1: convert(env_obs_spec)}
|
36
|
-
else:
|
37
|
-
self.observation_spec = {}
|
38
|
-
for i, obs_spec in enumerate(env_obs_spec.values()):
|
39
|
-
self.observation_spec[i + 1] = convert(obs_spec)
|
40
|
-
|
41
|
-
assert isinstance(env.action_spec(), specs.Array), (
|
42
|
-
"Only a single action type is supported."
|
43
|
-
)
|
44
|
-
self.action_spec = {1: convert(env.action_spec())}
|
45
|
-
|
46
|
-
self.observation_manager = spec_manager.SpecManager(self.observation_spec)
|
47
|
-
self.action_manager = spec_manager.SpecManager(self.action_spec)
|
48
|
-
|
49
|
-
|
50
|
-
class EnvironmentService(dm_env_rpc_pb2_grpc.EnvironmentServicer):
|
51
|
-
"""Runs the environment as a gRPC EnvironmentServicer."""
|
52
|
-
|
53
|
-
def __init__(self, env_type: Type[dm_env.Environment]) -> None:
|
54
|
-
"""Initializes the environment.
|
55
|
-
|
56
|
-
Args:
|
57
|
-
env_type: A dm_env class to serve.
|
58
|
-
"""
|
59
|
-
self._env_type = env_type
|
60
|
-
self._envs: dict[str, dm_env.Environment] = {}
|
61
|
-
self._specs: dict[str, EnvironmentSpec] = {}
|
62
|
-
self._joined_worlds: set[str] = set()
|
63
|
-
self._browser: playwright_crawler.PlaywrightBrowser = None
|
64
|
-
self._lock = threading.Lock()
|
65
|
-
self._num_worlds = 0
|
66
|
-
|
67
|
-
def Process(
|
68
|
-
self,
|
69
|
-
request_iterator: Iterable[dm_env_rpc_pb2.EnvironmentRequest],
|
70
|
-
context: grpc.ServicerContext,
|
71
|
-
):
|
72
|
-
"""Processes incoming EnvironmentRequests.
|
73
|
-
|
74
|
-
For each EnvironmentRequest the internal message is extracted and handled.
|
75
|
-
The response for that message is then placed in a EnvironmentResponse which
|
76
|
-
is returned to the client.
|
77
|
-
|
78
|
-
An error status will be returned if an unknown message type is received or
|
79
|
-
if the message is invalid for the current world state.
|
80
|
-
|
81
|
-
|
82
|
-
Args:
|
83
|
-
request_iterator: Message iterator provided by gRPC.
|
84
|
-
context: Context provided by gRPC.
|
85
|
-
|
86
|
-
Yields:
|
87
|
-
EnvironmentResponse: Response for each incoming EnvironmentRequest.
|
88
|
-
"""
|
89
|
-
cur_world = None
|
90
|
-
for request in request_iterator:
|
91
|
-
environment_response = dm_env_rpc_pb2.EnvironmentResponse()
|
92
|
-
try:
|
93
|
-
message_type = request.WhichOneof("payload")
|
94
|
-
internal_request = getattr(request, message_type)
|
95
|
-
match type(internal_request):
|
96
|
-
case dm_env_rpc_pb2.CreateWorldRequest:
|
97
|
-
response = self._handle_create_world_request(internal_request)
|
98
|
-
case dm_env_rpc_pb2.JoinWorldRequest:
|
99
|
-
response, cur_world = self._handle_join_world_request(
|
100
|
-
internal_request
|
101
|
-
)
|
102
|
-
case dm_env_rpc_pb2.LeaveWorldRequest:
|
103
|
-
response = self._handle_leave_world_request(
|
104
|
-
internal_request, cur_world
|
105
|
-
)
|
106
|
-
case dm_env_rpc_pb2.DestroyWorldRequest:
|
107
|
-
response = self._handle_destroy_world_request(internal_request)
|
108
|
-
case dm_env_rpc_pb2.StepRequest:
|
109
|
-
response = self._handle_step_request(
|
110
|
-
internal_request, cur_world
|
111
|
-
)
|
112
|
-
case _:
|
113
|
-
raise ValueError(
|
114
|
-
f"Unsupported request type: {type(internal_request)}"
|
115
|
-
)
|
116
|
-
getattr(environment_response, message_type).CopyFrom(response)
|
117
|
-
except Exception as e: # pylint: disable=broad-except
|
118
|
-
environment_response.error.CopyFrom(
|
119
|
-
status_pb2.Status(code=code_pb2.INTERNAL, message=str(e))
|
120
|
-
)
|
121
|
-
yield environment_response
|
122
|
-
|
123
|
-
def _validate_settings(self, settings, valid_settings):
|
124
|
-
"""Validate the provided settings with list of valid setting keys."""
|
125
|
-
unrecognized_settings = [
|
126
|
-
setting for setting in settings if setting not in valid_settings
|
127
|
-
]
|
128
|
-
|
129
|
-
if unrecognized_settings:
|
130
|
-
raise ValueError(
|
131
|
-
"Unrecognized settings provided! Invalid settings:"
|
132
|
-
f" {unrecognized_settings}"
|
133
|
-
)
|
134
|
-
|
135
|
-
def _add_spec_to_response(
|
136
|
-
self, world_name: str, response: dm_env_rpc_pb2.EnvironmentResponse
|
137
|
-
):
|
138
|
-
"""Modifies given respose to include action/observation specifications."""
|
139
|
-
if not self._specs.get(world_name):
|
140
|
-
raise ValueError(f"Not found a spec for {world_name} world")
|
141
|
-
|
142
|
-
spec = self._specs[world_name]
|
143
|
-
for uid, action in spec.action_spec.items():
|
144
|
-
response.specs.actions[uid].CopyFrom(action)
|
145
|
-
for uid, observation in spec.observation_spec.items():
|
146
|
-
response.specs.observations[uid].CopyFrom(observation)
|
147
|
-
|
148
|
-
def _handle_create_world_request(
|
149
|
-
self, request: dm_env_rpc_pb2.CreateWorldRequest
|
150
|
-
) -> dm_env_rpc_pb2.CreateWorldResponse:
|
151
|
-
"""Handles create_world requests."""
|
152
|
-
self._validate_settings(request.settings, [])
|
153
|
-
del request
|
154
|
-
world_name = _DEFAULT_WORLD_NAME
|
155
|
-
with self._lock:
|
156
|
-
if self._browser is None:
|
157
|
-
self._browser = playwright_crawler.PlaywrightBrowser()
|
158
|
-
else:
|
159
|
-
world_name += f"_{self._num_worlds}"
|
160
|
-
self._num_worlds += 1
|
161
|
-
|
162
|
-
new_context = self._browser.get_new_context()
|
163
|
-
env = self._env_type(new_context)
|
164
|
-
spec = EnvironmentSpec(env)
|
165
|
-
self._envs[world_name] = env
|
166
|
-
self._specs[world_name] = spec
|
167
|
-
|
168
|
-
return dm_env_rpc_pb2.CreateWorldResponse(world_name=world_name)
|
169
|
-
|
170
|
-
def _handle_join_world_request(
|
171
|
-
self, request: dm_env_rpc_pb2.JoinWorldRequest
|
172
|
-
) -> tuple[dm_env_rpc_pb2.JoinWorldResponse, str]:
|
173
|
-
"""Handles join_world requests."""
|
174
|
-
self._validate_settings(request.settings, [])
|
175
|
-
response = dm_env_rpc_pb2.JoinWorldResponse()
|
176
|
-
world_name = request.world_name
|
177
|
-
with self._lock:
|
178
|
-
if not self._envs.get(world_name):
|
179
|
-
raise ValueError(f"Joining with the wrong world_name {world_name}")
|
180
|
-
if world_name in self._joined_worlds:
|
181
|
-
raise ValueError(f"Only one client can joint the world {world_name}")
|
182
|
-
self._joined_worlds.add(world_name)
|
183
|
-
self._add_spec_to_response(world_name, response)
|
184
|
-
|
185
|
-
del request
|
186
|
-
return (response, world_name)
|
187
|
-
|
188
|
-
def _handle_leave_world_request(
|
189
|
-
self, request: dm_env_rpc_pb2.LeaveWorldRequest, world_name: str
|
190
|
-
) -> dm_env_rpc_pb2.LeaveWorldResponse:
|
191
|
-
"""Handles leave_world requests."""
|
192
|
-
del request
|
193
|
-
if world_name in self._joined_worlds:
|
194
|
-
self._joined_worlds.remove(world_name)
|
195
|
-
response = dm_env_rpc_pb2.LeaveWorldResponse()
|
196
|
-
return response
|
197
|
-
|
198
|
-
def _handle_destroy_world_request(
|
199
|
-
self, request: dm_env_rpc_pb2.DestroyWorldRequest
|
200
|
-
) -> dm_env_rpc_pb2.DestroyWorldResponse:
|
201
|
-
"""Handles destroy_world requests."""
|
202
|
-
world_name = request.world_name
|
203
|
-
del request
|
204
|
-
with self._lock:
|
205
|
-
if not self._envs.get(world_name):
|
206
|
-
raise ValueError("Can not destroy uncreated environment.")
|
207
|
-
if world_name in self._joined_worlds:
|
208
|
-
raise ValueError("Can not destroy environment with a joined agent.")
|
209
|
-
env = self._envs.pop(world_name)
|
210
|
-
env.close()
|
211
|
-
env = None
|
212
|
-
self._specs.pop(world_name, None)
|
213
|
-
|
214
|
-
if not self._envs:
|
215
|
-
self._browser.close()
|
216
|
-
self._browser = None
|
217
|
-
response = dm_env_rpc_pb2.DestroyWorldResponse()
|
218
|
-
return response
|
219
|
-
|
220
|
-
def _handle_step_request(
|
221
|
-
self, request: dm_env_rpc_pb2.StepRequest, cur_world: str
|
222
|
-
) -> dm_env_rpc_pb2.StepResponse:
|
223
|
-
"""Handles step requests.
|
224
|
-
|
225
|
-
Args:
|
226
|
-
request: The request, which should contain a 'command' entry.
|
227
|
-
cur_world: The name of the world in which we're making a step.
|
228
|
-
|
229
|
-
Returns:
|
230
|
-
Response including requested observations.
|
231
|
-
|
232
|
-
Raises:
|
233
|
-
KeyError: If the requested observation is not in the list of available
|
234
|
-
observations.
|
235
|
-
"""
|
236
|
-
with self._lock:
|
237
|
-
assert cur_world in self._envs, (
|
238
|
-
"Current world does not have an assosiated environment"
|
239
|
-
)
|
240
|
-
assert cur_world in self._joined_worlds, (
|
241
|
-
"Please join world before calling step."
|
242
|
-
)
|
243
|
-
env = self._envs[cur_world]
|
244
|
-
spec = self._specs[cur_world]
|
245
|
-
|
246
|
-
action = spec.action_manager.unpack(request.actions)
|
247
|
-
|
248
|
-
if "command" in action:
|
249
|
-
command = action["command"]
|
250
|
-
else:
|
251
|
-
# For some reason dm_env calls step without actions after a reset.
|
252
|
-
command = ""
|
253
|
-
|
254
|
-
timestep: dm_env.TimeStep = env.step(command)
|
255
|
-
|
256
|
-
packed_observations = spec.observation_manager.pack(timestep.observation)
|
257
|
-
|
258
|
-
match timestep.step_type:
|
259
|
-
case dm_env.StepType.MID:
|
260
|
-
step_state = dm_env_rpc_pb2.RUNNING
|
261
|
-
case dm_env.StepType.LAST:
|
262
|
-
step_state = dm_env_rpc_pb2.TERMINATED
|
263
|
-
case _:
|
264
|
-
raise ValueError(f"Unsupported step type {timestep.step_type}.")
|
265
|
-
|
266
|
-
response = dm_env_rpc_pb2.StepResponse(state=step_state)
|
267
|
-
for requested_observation in request.requested_observations:
|
268
|
-
if requested_observation not in packed_observations:
|
269
|
-
name = spec.observation_manager.uid_to_name(requested_observation)
|
270
|
-
raise KeyError(f"Requested observation not found: {name}")
|
271
|
-
response.observations[requested_observation].CopyFrom(
|
272
|
-
packed_observations[requested_observation]
|
273
|
-
)
|
274
|
-
|
275
|
-
return response
|
Binary file
|