inspect-ai 0.3.75__py3-none-any.whl → 0.3.77__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.
Files changed (87) hide show
  1. inspect_ai/_cli/eval.py +16 -0
  2. inspect_ai/_display/core/results.py +6 -1
  3. inspect_ai/_eval/eval.py +8 -1
  4. inspect_ai/_eval/evalset.py +6 -2
  5. inspect_ai/_eval/registry.py +3 -5
  6. inspect_ai/_eval/run.py +7 -2
  7. inspect_ai/_eval/task/run.py +4 -0
  8. inspect_ai/_util/content.py +3 -0
  9. inspect_ai/_util/logger.py +3 -0
  10. inspect_ai/_view/www/dist/assets/index.css +28 -16
  11. inspect_ai/_view/www/dist/assets/index.js +4811 -4609
  12. inspect_ai/_view/www/log-schema.json +79 -9
  13. inspect_ai/_view/www/src/samples/chat/tools/ToolCallView.tsx +22 -4
  14. inspect_ai/_view/www/src/samples/chat/tools/ToolInput.tsx +1 -1
  15. inspect_ai/_view/www/src/samples/descriptor/score/CategoricalScoreDescriptor.tsx +1 -1
  16. inspect_ai/_view/www/src/samples/descriptor/score/NumericScoreDescriptor.tsx +2 -2
  17. inspect_ai/_view/www/src/samples/sample-tools/SortFilter.tsx +1 -1
  18. inspect_ai/_view/www/src/samples/transcript/ModelEventView.module.css +2 -2
  19. inspect_ai/_view/www/src/types/log.d.ts +11 -5
  20. inspect_ai/log/_recorders/json.py +8 -0
  21. inspect_ai/log/_transcript.py +13 -4
  22. inspect_ai/model/_call_tools.py +13 -4
  23. inspect_ai/model/_chat_message.py +3 -0
  24. inspect_ai/model/_model.py +5 -1
  25. inspect_ai/model/_model_output.py +6 -1
  26. inspect_ai/model/_openai.py +78 -10
  27. inspect_ai/model/_openai_responses.py +277 -0
  28. inspect_ai/model/_providers/anthropic.py +134 -75
  29. inspect_ai/model/_providers/azureai.py +2 -2
  30. inspect_ai/model/_providers/mistral.py +29 -13
  31. inspect_ai/model/_providers/openai.py +64 -57
  32. inspect_ai/model/_providers/openai_responses.py +177 -0
  33. inspect_ai/model/_providers/openrouter.py +52 -2
  34. inspect_ai/model/_providers/providers.py +1 -1
  35. inspect_ai/model/_providers/vertex.py +5 -2
  36. inspect_ai/tool/__init__.py +6 -0
  37. inspect_ai/tool/_tool.py +23 -3
  38. inspect_ai/tool/_tool_call.py +5 -2
  39. inspect_ai/tool/_tool_support_helpers.py +200 -0
  40. inspect_ai/tool/_tools/_bash_session.py +119 -0
  41. inspect_ai/tool/_tools/_computer/_computer.py +1 -1
  42. inspect_ai/tool/_tools/_text_editor.py +121 -0
  43. inspect_ai/tool/_tools/_think.py +48 -0
  44. inspect_ai/tool/_tools/_web_browser/_back_compat.py +150 -0
  45. inspect_ai/tool/_tools/_web_browser/_web_browser.py +75 -130
  46. inspect_ai/tool/_tools/_web_search.py +1 -1
  47. inspect_ai/util/_json.py +28 -0
  48. inspect_ai/util/_sandbox/context.py +16 -7
  49. inspect_ai/util/_sandbox/docker/config.py +1 -1
  50. inspect_ai/util/_sandbox/docker/internal.py +3 -3
  51. {inspect_ai-0.3.75.dist-info → inspect_ai-0.3.77.dist-info}/METADATA +5 -2
  52. {inspect_ai-0.3.75.dist-info → inspect_ai-0.3.77.dist-info}/RECORD +56 -80
  53. {inspect_ai-0.3.75.dist-info → inspect_ai-0.3.77.dist-info}/WHEEL +1 -1
  54. inspect_ai/model/_image.py +0 -15
  55. inspect_ai/tool/_tools/_web_browser/_resources/.pylintrc +0 -8
  56. inspect_ai/tool/_tools/_web_browser/_resources/.vscode/launch.json +0 -24
  57. inspect_ai/tool/_tools/_web_browser/_resources/.vscode/settings.json +0 -25
  58. inspect_ai/tool/_tools/_web_browser/_resources/Dockerfile +0 -22
  59. inspect_ai/tool/_tools/_web_browser/_resources/README.md +0 -63
  60. inspect_ai/tool/_tools/_web_browser/_resources/accessibility_tree.py +0 -71
  61. inspect_ai/tool/_tools/_web_browser/_resources/accessibility_tree_node.py +0 -323
  62. inspect_ai/tool/_tools/_web_browser/_resources/cdp/__init__.py +0 -5
  63. inspect_ai/tool/_tools/_web_browser/_resources/cdp/a11y.py +0 -279
  64. inspect_ai/tool/_tools/_web_browser/_resources/cdp/dom.py +0 -9
  65. inspect_ai/tool/_tools/_web_browser/_resources/cdp/dom_snapshot.py +0 -293
  66. inspect_ai/tool/_tools/_web_browser/_resources/cdp/page.py +0 -94
  67. inspect_ai/tool/_tools/_web_browser/_resources/constants.py +0 -2
  68. inspect_ai/tool/_tools/_web_browser/_resources/images/usage_diagram.svg +0 -2
  69. inspect_ai/tool/_tools/_web_browser/_resources/mock_environment.py +0 -45
  70. inspect_ai/tool/_tools/_web_browser/_resources/playwright_browser.py +0 -50
  71. inspect_ai/tool/_tools/_web_browser/_resources/playwright_crawler.py +0 -48
  72. inspect_ai/tool/_tools/_web_browser/_resources/playwright_page_crawler.py +0 -280
  73. inspect_ai/tool/_tools/_web_browser/_resources/pyproject.toml +0 -65
  74. inspect_ai/tool/_tools/_web_browser/_resources/rectangle.py +0 -64
  75. inspect_ai/tool/_tools/_web_browser/_resources/rpc_client_helpers.py +0 -146
  76. inspect_ai/tool/_tools/_web_browser/_resources/scale_factor.py +0 -64
  77. inspect_ai/tool/_tools/_web_browser/_resources/test_accessibility_tree_node.py +0 -180
  78. inspect_ai/tool/_tools/_web_browser/_resources/test_playwright_crawler.py +0 -99
  79. inspect_ai/tool/_tools/_web_browser/_resources/test_rectangle.py +0 -15
  80. inspect_ai/tool/_tools/_web_browser/_resources/test_web_client.py +0 -44
  81. inspect_ai/tool/_tools/_web_browser/_resources/web_browser_rpc_types.py +0 -39
  82. inspect_ai/tool/_tools/_web_browser/_resources/web_client.py +0 -214
  83. inspect_ai/tool/_tools/_web_browser/_resources/web_client_new_session.py +0 -35
  84. inspect_ai/tool/_tools/_web_browser/_resources/web_server.py +0 -192
  85. {inspect_ai-0.3.75.dist-info → inspect_ai-0.3.77.dist-info}/entry_points.txt +0 -0
  86. {inspect_ai-0.3.75.dist-info → inspect_ai-0.3.77.dist-info/licenses}/LICENSE +0 -0
  87. {inspect_ai-0.3.75.dist-info → inspect_ai-0.3.77.dist-info}/top_level.txt +0 -0
@@ -1,71 +0,0 @@
1
- from functools import reduce
2
- from typing import Iterable, TypedDict
3
-
4
- from accessibility_tree_node import AccessibilityTreeNode
5
- from cdp.a11y import AXNode, AXNodeId
6
- from cdp.dom_snapshot import DOMSnapshot, create_snapshot_context
7
- from rectangle import Rectangle
8
-
9
- _AccType = tuple[
10
- AXNode | None,
11
- dict[AXNodeId, AXNode],
12
- ]
13
-
14
-
15
- class AccessibilityTree(TypedDict):
16
- root: AccessibilityTreeNode
17
- nodes: dict[AXNodeId, AccessibilityTreeNode]
18
-
19
-
20
- def create_accessibility_tree(
21
- *,
22
- ax_nodes: Iterable[AXNode],
23
- dom_snapshot: DOMSnapshot,
24
- device_scale_factor: float,
25
- window_bounds: Rectangle,
26
- ) -> AccessibilityTree | None:
27
- """
28
- Creates an accessibility tree from the given Chrome DevTools Protocol AX nodes and DOM snapshot.
29
-
30
- Args:
31
- ax_nodes (Iterable[AXNode]): An iterable of AXNode objects representing the accessibility nodes.
32
- dom_snapshot (DOMSnapshot): A snapshot of the DOM at the time of accessibility tree creation.
33
- device_scale_factor (float): The scale factor of the device.
34
- window_bounds (Bounds): The bounds of the window.
35
-
36
- Returns:
37
- AccessibilityTree: The accessibility tree.
38
- """
39
-
40
- # first make a dict of AXNodeId's to AXNode's and find the root on the way
41
- def reducer(acc: _AccType, ax_node: AXNode) -> _AccType:
42
- root_node, nodes = acc
43
- nodes[ax_node.nodeId] = ax_node
44
- return (
45
- # TODO: What do we want for multiple roots?
46
- root_node or (ax_node if ax_node.parentId is None else None),
47
- nodes,
48
- )
49
-
50
- initial_acc: _AccType = (None, {}) # The inference engine is weak
51
- root_node, nodes = reduce(reducer, ax_nodes, initial_acc)
52
-
53
- if not root_node:
54
- return None
55
-
56
- # Now create the AccessibilityTreeNode hierarchy
57
- snapshot_context = create_snapshot_context(dom_snapshot)
58
- all_accessibility_tree_nodes: dict[AXNodeId, AccessibilityTreeNode] = {}
59
-
60
- return AccessibilityTree(
61
- root=AccessibilityTreeNode(
62
- ax_node=root_node,
63
- ax_nodes=nodes,
64
- parent=None,
65
- all_accessibility_tree_nodes=all_accessibility_tree_nodes,
66
- snapshot_context=snapshot_context,
67
- device_scale_factor=device_scale_factor,
68
- window_bounds=window_bounds,
69
- ),
70
- nodes=all_accessibility_tree_nodes,
71
- )
@@ -1,323 +0,0 @@
1
- from functools import reduce
2
- from typing import Optional
3
-
4
- from cdp.a11y import (
5
- AXNode,
6
- AXNodeId,
7
- AXProperty,
8
- AXPropertyName,
9
- node_has_property,
10
- string_from_ax_value,
11
- )
12
- from cdp.dom_snapshot import (
13
- DOMSnapshotContext,
14
- bounds_for_node_index,
15
- current_url_src_for_node_index,
16
- index_for_node_id,
17
- text_value_for_node_index,
18
- )
19
- from rectangle import Rectangle
20
-
21
- # Properties to ignore when printing out the accessibility tree.
22
- _IGNORED_AT_PROPERTIES: set[AXPropertyName] = {
23
- "focusable",
24
- "readonly",
25
- "level",
26
- "settable",
27
- "multiline",
28
- "invalid",
29
- }
30
-
31
- # The AXRole enum is here (https://chromium.googlesource.com/chromium/chromium/+/HEAD/ui/accessibility/ax_enums.idl#62)
32
- _ROLES_IGNORED = {"horizontal_rule", "ignored"}
33
-
34
- # These are noisy and provide no real benefit unless there's a name associated with them
35
- _ROLES_REQUIRING_A_NAME = {"generic", "paragraph", "div"}
36
-
37
- # Used for debugging, include the elements bounding rect during output.
38
- _INCLUDE_BOUNDS_IN_OUTPUT = False
39
-
40
-
41
- class AccessibilityTreeNode:
42
- def __init__(
43
- self,
44
- *,
45
- ax_node: AXNode,
46
- ax_nodes: dict[AXNodeId, AXNode],
47
- parent: Optional["AccessibilityTreeNode"],
48
- all_accessibility_tree_nodes: dict[AXNodeId, "AccessibilityTreeNode"],
49
- snapshot_context: DOMSnapshotContext,
50
- device_scale_factor: float,
51
- window_bounds: Rectangle,
52
- ):
53
- all_accessibility_tree_nodes[ax_node.nodeId] = self
54
-
55
- self._is_expanded = node_has_property(ax_node, "expanded")
56
- self._ax_node = ax_node
57
- self._parent = parent
58
- self._is_ignored: bool | None = None
59
- self._bounds: Rectangle | None = None
60
- self._is_visible: bool | None = None
61
- self._current_src_url: str | None = None
62
- self._input: str | None = None
63
- self.__closest_non_ignored_parent: AccessibilityTreeNode | None = None
64
-
65
- dom_node_index = (
66
- index_for_node_id(ax_node.backendDOMNodeId, snapshot_context)
67
- if ax_node.backendDOMNodeId
68
- else None
69
- )
70
-
71
- # The following three variables (_bounds, _is_visible, and _current_src_url) depend on
72
- # info from the DOM element associated with this AXNode
73
- if dom_node_index:
74
- self._bounds = (
75
- Rectangle(*layout_bounds).scale(1 / device_scale_factor)
76
- if (
77
- layout_bounds := bounds_for_node_index(
78
- dom_node_index, snapshot_context
79
- )
80
- )
81
- is not None
82
- else None
83
- )
84
- self._is_visible = (
85
- (self._bounds.has_area and self._bounds.overlaps(window_bounds))
86
- if self._bounds
87
- else None
88
- )
89
- self._current_src_url = (
90
- current_url_src_for_node_index(dom_node_index, snapshot_context)
91
- if self.role == "image"
92
- else None
93
- )
94
-
95
- if node_has_property(ax_node, "editable"):
96
- # Sometimes a node will have it's input stored as 'value' other times we
97
- # need to go looking through the DOM tree to find its matching string.
98
- self._input = (
99
- string_from_ax_value(ax_node.value)
100
- if ax_node.value
101
- else text_value_for_node_index(dom_node_index, snapshot_context)
102
- if dom_node_index is not None
103
- else None
104
- )
105
-
106
- # this is a little awkward, but the old code ensured that menuitems were visible in given the following condition
107
- if self.role == "menuitem" and parent is not None and parent.is_expanded:
108
- self._is_visible = True
109
-
110
- # It's important to set all other properties before creating children in case
111
- # the children sample parent.xxx
112
- self.children = (
113
- [
114
- AccessibilityTreeNode(
115
- ax_node=ax_nodes[child_id],
116
- ax_nodes=ax_nodes,
117
- parent=self,
118
- all_accessibility_tree_nodes=all_accessibility_tree_nodes,
119
- snapshot_context=snapshot_context,
120
- device_scale_factor=device_scale_factor,
121
- window_bounds=window_bounds,
122
- )
123
- for child_id in ax_node.childIds
124
- ]
125
- if ax_node.childIds
126
- else None
127
- )
128
-
129
- def __str__(self) -> str:
130
- bounds_string = str(self.bounds) if _INCLUDE_BOUNDS_IN_OUTPUT else None
131
-
132
- input_string = f'Current input: "{self._input}"' if self._input else None
133
-
134
- name_string = f'"{self.name}"' if self.name else None
135
- # Without bounds we can't click on the element, and therefore the ID
136
- # can not be used. Still useful to show these elements though (e.g.)
137
- # to let user know about options.
138
- id_string = f"[{'*' if not self._bounds or not self._bounds.has_area else self._ax_node.nodeId}]"
139
- url_string = self._current_src_url if self._current_src_url else None
140
-
141
- url_string = _maybe_redact_base64_url(self._current_src_url)
142
-
143
- return " ".join(
144
- [
145
- item
146
- for item in [
147
- id_string,
148
- self.role,
149
- name_string,
150
- url_string,
151
- input_string,
152
- self._property_string(),
153
- bounds_string,
154
- ]
155
- if item is not None
156
- ]
157
- )
158
-
159
- @property
160
- def node_id(self) -> AXNodeId:
161
- return self._ax_node.nodeId
162
-
163
- @property
164
- def name(self) -> str:
165
- return string_from_ax_value(self._ax_node.name)
166
-
167
- @property
168
- def is_expanded(self) -> bool:
169
- return self._is_expanded
170
-
171
- @property
172
- def is_visible(self) -> bool:
173
- return self._is_visible or False
174
-
175
- @property
176
- def bounds(self) -> Rectangle | None:
177
- return self._bounds
178
-
179
- @property
180
- def current_src_url(self) -> str | None:
181
- return self._current_src_url
182
-
183
- @property
184
- def input(self) -> str | None:
185
- return self._input
186
-
187
- @property
188
- def is_ignored(self) -> bool:
189
- # computing _is_ignored is very expensive, do it on demand and remember the result
190
- if self._is_ignored is None:
191
- self._is_ignored = self._compute_is_ignored()
192
- return self._is_ignored
193
-
194
- @property
195
- def role(self) -> str:
196
- return string_from_ax_value(self._ax_node.role)
197
-
198
- @property
199
- def _closest_non_ignored_parent(self) -> Optional["AccessibilityTreeNode"]:
200
- if self._parent and self.__closest_non_ignored_parent is None:
201
- self.__closest_non_ignored_parent = (
202
- None
203
- if not self._parent
204
- else self._parent
205
- if not self._parent.is_ignored
206
- else self._parent._closest_non_ignored_parent # pylint: disable=protected-access
207
- )
208
- return self.__closest_non_ignored_parent
209
-
210
- def render_accessibility_tree(self, indent: int = 0) -> str:
211
- """Returns the string representation of the node and its children."""
212
- result = ""
213
- if not self.is_ignored:
214
- result = " " * indent + str(self) + "\n"
215
- indent += 1
216
- children_strings = [
217
- child.render_accessibility_tree(indent) for child in self.children or []
218
- ]
219
- result = result + "".join([s for s in children_strings if s])
220
- return result
221
-
222
- def render_main_content(self) -> str | None:
223
- return (
224
- main_node._extract_text() # pylint: disable=protected-access
225
- if (main_node := self._get_main_content_node())
226
- else None
227
- )
228
-
229
- def _compute_is_ignored(self) -> bool:
230
- if self._ax_node.ignored or not self.is_visible or self.role in _ROLES_IGNORED:
231
- return True
232
-
233
- # if the parent is a link/button with a name, and this node is within the parent's bounds, we can ignore it.
234
- if (
235
- (closest_parent := self._closest_non_ignored_parent)
236
- and closest_parent.bounds
237
- and self.bounds
238
- and (closest_parent.role == "link" or closest_parent.role == "button")
239
- and closest_parent.name
240
- and self.bounds.within(closest_parent.bounds)
241
- ):
242
- return True
243
-
244
- if bool(self.name): # excludes None or ""
245
- # This simplifies things like links/buttons whose contents are sub-elements
246
- # e.g.
247
- # [158] button "Menu"
248
- # [1166] StaticText "Menu"
249
- # becomes
250
- # [158] button "Menu"
251
- return self._parent is not None and self._parent.name == self.name
252
-
253
- return self.role in _ROLES_REQUIRING_A_NAME
254
-
255
- def _get_main_content_node(self) -> Optional["AccessibilityTreeNode"]:
256
- if self.role == "main":
257
- return self
258
-
259
- return next(
260
- (
261
- result
262
- for child in self.children or []
263
- if (result := child._get_main_content_node()) # pylint: disable=protected-access
264
- ),
265
- None,
266
- )
267
-
268
- def _extract_text(self):
269
- """
270
- Recursively extracts and concatenates text from the accessibility tree nodes.
271
-
272
- This method traverses the accessibility tree starting from the current node,
273
- concatenating the text of each node.
274
-
275
- Returns:
276
- str: The concatenated text from the accessibility tree nodes, with
277
- appropriate spacing based on node roles.
278
- """
279
- if not self.is_visible:
280
- return ""
281
-
282
- def reducer(acc, child):
283
- child_text = child._extract_text() # pylint: disable=protected-access
284
- return (
285
- acc
286
- + (
287
- "\n\n"
288
- if child.role
289
- in {"paragraph", "note", "row", "div", "heading", "region"}
290
- else " "
291
- )
292
- + child_text
293
- )
294
-
295
- return reduce(reducer, self.children or [], self.name).strip()
296
-
297
- def _property_string(self) -> str:
298
- properties = [
299
- _prop_name_value_str(node_property)
300
- if node_property.value.value
301
- else f"{node_property.name}"
302
- for node_property in self._ax_node.properties or ()
303
- if node_property.name not in _IGNORED_AT_PROPERTIES
304
- ]
305
- return " [" + ", ".join(properties) + "]" if properties else ""
306
-
307
-
308
- def _prop_name_value_str(node_property: AXProperty) -> str:
309
- # pre-commit hook (debug-statements) crashes if value is inlined below
310
- value = (
311
- _maybe_redact_base64_url(node_property.value.value)
312
- if node_property.name == "url" and isinstance(node_property.value.value, str)
313
- else node_property.value.value
314
- )
315
- return f"{node_property.name}: {value}"
316
-
317
-
318
- def _maybe_redact_base64_url(url: str | None) -> str | None:
319
- return (
320
- "data:<base64-data-removed>"
321
- if url and url.startswith("data:") and "base64" in url
322
- else url
323
- )
@@ -1,5 +0,0 @@
1
- """
2
- Types and pure functional helpers associated with Chrome DevTools Protocol
3
-
4
- https://chromedevtools.github.io/devtools-protocol/
5
- """
@@ -1,279 +0,0 @@
1
- """
2
- Types and pure functional helpers associated with Chrome DevTools Protocol's 'Accessibility' Domain
3
-
4
- https://chromedevtools.github.io/devtools-protocol/tot/Accessibility/
5
- """
6
-
7
- from typing import Literal, NewType, Optional
8
-
9
- from pydantic import BaseModel
10
-
11
- from cdp.dom import DOMBackendNodeId
12
-
13
- # brand these str's so that we don't confuse them with other str's
14
- PageFrameId = NewType("PageFrameId", str)
15
- AXNodeId = NewType("AXNodeId", str)
16
-
17
-
18
- AXPropertyName = Literal[
19
- "actions",
20
- "busy",
21
- "disabled",
22
- "editable",
23
- "focusable",
24
- "focused",
25
- "hidden",
26
- "hiddenRoot",
27
- "invalid",
28
- "keyshortcuts",
29
- "settable",
30
- "roledescription",
31
- "live",
32
- "atomic",
33
- "relevant",
34
- "root",
35
- "autocomplete",
36
- "hasPopup",
37
- "level",
38
- "multiselectable",
39
- "orientation",
40
- "multiline",
41
- "readonly",
42
- "required",
43
- "valuemin",
44
- "valuemax",
45
- "valuetext",
46
- "checked",
47
- "expanded",
48
- "modal",
49
- "pressed",
50
- "selected",
51
- "activedescendant",
52
- "controls",
53
- "describedby",
54
- "details",
55
- "errormessage",
56
- "flowto",
57
- "labelledby",
58
- "owns",
59
- "url",
60
- ]
61
-
62
-
63
- AXValueType = Literal[
64
- "boolean",
65
- "tristate",
66
- "booleanOrUndefined",
67
- "idref",
68
- "idrefList",
69
- "integer",
70
- "node",
71
- "nodeList",
72
- "number",
73
- "string",
74
- "computedString",
75
- "token",
76
- "tokenList",
77
- "domRelation",
78
- "role",
79
- "internalRole",
80
- "valueUndefined",
81
- ]
82
-
83
-
84
- class AXRelatedNode(BaseModel, frozen=True):
85
- backendDOMNodeId: DOMBackendNodeId
86
- """The BackendNodeId of the related DOM node."""
87
- idref: str | None = None
88
- """The IDRef value provided, if any."""
89
- text: str | None = None
90
- """The text alternative of this node in the current context."""
91
-
92
-
93
- AXValueSourceType = Literal[
94
- "attribute", "implicit", "stylet", "contents", "placeholder", "relatedElement"
95
- ]
96
-
97
- AXValueNativeSourceType = Literal[
98
- "description",
99
- "figcaption",
100
- "label",
101
- "labelfor",
102
- "labelwrapped",
103
- "legend",
104
- "rubyannotation",
105
- "tablecaption",
106
- "title",
107
- "other",
108
- ]
109
-
110
-
111
- class AXValueSource(BaseModel, frozen=True):
112
- """A single source for a computed AX property."""
113
-
114
- type: AXValueSourceType
115
- """What type of source this is."""
116
- value: Optional["AXValue"] = None
117
- """The value of this property source."""
118
- attribute: str | None = None
119
- """The name of the relevant attribute, if any."""
120
- attributeValue: Optional["AXValue"] = None
121
- """The value of the relevant attribute, if any."""
122
- superseded: bool | None = None
123
- """Whether this source is superseded by a higher priority source."""
124
- nativeSource: AXValueNativeSourceType | None = None
125
- """The native markup source for this value, e.g. a `<label>` element."""
126
- nativeSourceValue: Optional["AXValue"] = None
127
- """The value, such as a node or node list, of the native source."""
128
- invalid: bool | None = None
129
- """Whether the value for this property is invalid."""
130
- invalidReason: str | None = None
131
- """Reason for the value being invalid, if it is."""
132
-
133
-
134
- class AXValue(BaseModel, frozen=True):
135
- """A single computed AX property."""
136
-
137
- type: AXValueType
138
- """The type of this value."""
139
- value: object | None = None
140
- """The computed value of this property."""
141
- relatedNodes: tuple[AXRelatedNode, ...] | None = None
142
- """One or more related nodes, if applicable."""
143
- sources: tuple[AXValueSource, ...] | None = None
144
- """The sources which contributed to the computation of this property."""
145
-
146
-
147
- class AXProperty(BaseModel, frozen=True):
148
- name: AXPropertyName
149
- """The name of this property."""
150
- value: AXValue
151
- """The value of this property."""
152
-
153
-
154
- # AXNode's ignoredReasons is documented to be a list[AXProperty]. However, that seems to
155
- # be erroneous since the namespace for ignored reason names is completely separate from property names.
156
- class AXIgnoredReason(BaseModel, frozen=True):
157
- # name: Literal[
158
- # "uninteresting",
159
- # "ariaHiddenSubtree",
160
- # "ariaHiddenElement",
161
- # "notRendered",
162
- # "notVisible",
163
- # "emptyAlt",
164
- # ]
165
- name: str # since this is all undocumented, it's best to be loose
166
- """The name of this ignored reason."""
167
- value: AXValue
168
- """The value of this ignored reason."""
169
-
170
-
171
- class AXNode(BaseModel, frozen=True):
172
- """A node in the accessibility tree."""
173
-
174
- nodeId: AXNodeId
175
- """Unique identifier for this node."""
176
- ignored: bool
177
- """Wether this node is ignore3d for accessibility"""
178
- ignoredReasons: tuple[AXIgnoredReason, ...] | None = None
179
- """Collection of reasons why this node is hidden."""
180
- role: AXValue | None = None
181
- """This `Node`'s role, wether explicit or implicit."""
182
- chromeRole: AXValue | None = None
183
- """This `Node`'s Chrome raw role."""
184
- name: AXValue | None = None
185
- """The accessible name for this `Node`."""
186
- description: AXValue | None = None
187
- """The accessible description for this `Node`."""
188
- value: AXValue | None = None
189
- """The value for this `Node`."""
190
- properties: tuple[AXProperty, ...] | None = None
191
- """All other properties"""
192
- parentId: AXNodeId | None = None
193
- """ID for this node's parent."""
194
- childIds: tuple[AXNodeId, ...] | None = None
195
- """IDs for each of this node's child nodes."""
196
- backendDOMNodeId: DOMBackendNodeId | None = None
197
- """The backend ID for the associated DOM node, if any."""
198
- frameId: PageFrameId | None = None
199
- """The frame ID for the frame associated with this nodes document."""
200
-
201
-
202
- class AXTree(BaseModel, frozen=True):
203
- nodes: tuple[AXNode, ...]
204
-
205
-
206
- # CDP defines `AXValue.value` as `Any`, so this is a coercion function
207
- def string_from_ax_value(value: AXValue | None) -> str:
208
- """
209
- Coerces an AXValue to a string.
210
-
211
- Args:
212
- value (AXValue | None): The AXValue to be coerced.
213
-
214
- Returns:
215
- str: The string representation of the AXValue, or an empty string if the value is None or not a string.
216
- """
217
- return value.value if value and isinstance(value.value, str) else ""
218
-
219
-
220
- def node_has_property(node: AXNode, property_name: AXPropertyName) -> bool:
221
- """
222
- Checks if an AXNode has a specific property.
223
-
224
- Args:
225
- node (AXNode): The AXNode to check.
226
- property_name (AXPropertyName): The name of the property to look for.
227
-
228
- Returns:
229
- bool: True if the node has the property, False otherwise.
230
- """
231
- return node_property(node, property_name) is not None
232
-
233
-
234
- def node_property(node: AXNode, property_name: AXPropertyName) -> AXProperty | None:
235
- """
236
- Retrieves a specific property from an AXNode.
237
-
238
- Args:
239
- node (AXNode): The AXNode to retrieve the property from.
240
- property_name (AXPropertyName): The name of the property to retrieve.
241
-
242
- Returns:
243
- AXProperty | None: The property if found, otherwise None.
244
- """
245
- return next(
246
- (prop for prop in node.properties or () if prop.name == property_name), None
247
- )
248
-
249
-
250
- def node_bool_property(node: AXNode, property_name: AXPropertyName) -> bool:
251
- """
252
- Retrieves a boolean property from an AXNode.
253
-
254
- Args:
255
- node (AXNode): The AXNode to retrieve the property from.
256
- property_name (AXPropertyName): The name of the property to retrieve.
257
-
258
- Returns:
259
- bool: The boolean value of the property, or False if the property is not found or not a boolean.
260
- """
261
- return bool(prop.value) if (prop := node_property(node, property_name)) else False
262
-
263
-
264
- def node_str_property(node: AXNode, property_name: AXPropertyName) -> str | None:
265
- """
266
- Retrieves a str property from an AXNode.
267
-
268
- Args:
269
- node (AXNode): The AXNode to retrieve the property from.
270
- property_name (AXPropertyName): The name of the property to retrieve.
271
-
272
- Returns:
273
- str: The str value of the property, or None if the property is not found or not a str.
274
- """
275
- prop = node_property(node, property_name)
276
- if prop is None:
277
- return None
278
- v = prop.value
279
- return v.value if isinstance(v.value, str) else None
@@ -1,9 +0,0 @@
1
- """
2
- Types associated with Chrome DevTools Protocol's 'DOM' Domain
3
-
4
- https://chromedevtools.github.io/devtools-protocol/tot/DOM/
5
- """
6
-
7
- from typing import NewType
8
-
9
- DOMBackendNodeId = NewType("DOMBackendNodeId", int)