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,293 +0,0 @@
1
- """
2
- Types and pure functional helpers associated with Chrome DevTools Protocol's 'DOMSnapshot' Domain
3
-
4
- https://chromedevtools.github.io/devtools-protocol/tot/DOMSnapshot/
5
- """
6
-
7
- from typing import Literal, NewType, TypedDict
8
-
9
- from pydantic import BaseModel
10
-
11
- from cdp.dom import DOMBackendNodeId
12
-
13
- StringIndex = NewType("StringIndex", int)
14
- Rectangle = tuple[float, ...]
15
- ArrayOfStrings = tuple[StringIndex, ...]
16
-
17
-
18
- class RareStringData(BaseModel, frozen=True):
19
- """Data that is only present on rare nodes."""
20
-
21
- index: tuple[int, ...]
22
- value: tuple[StringIndex, ...]
23
-
24
-
25
- class RareIntegerData(BaseModel, frozen=True):
26
- index: tuple[int, ...]
27
- value: tuple[int, ...]
28
-
29
-
30
- class RareBooleanData(BaseModel, frozen=True):
31
- index: tuple[int, ...]
32
-
33
-
34
- class NodeTreeSnapshot(BaseModel, frozen=True):
35
- """Table containing nodes."""
36
-
37
- parentIndex: tuple[int, ...] | None = None
38
- """Parent node index."""
39
- nodeType: tuple[int, ...] | None = None
40
- """`Node`'s nodeType"""
41
- shadowRootType: RareStringData | None = None
42
- """Type of the shadow root the `Node` is in. String values are equal to the `ShadowRootType` enum."""
43
- nodeName: tuple[StringIndex, ...] | None = None
44
- """`Node`'s nodeName."""
45
- nodeValue: tuple[StringIndex, ...] | None = None
46
- """`Node`'s nodeValue."""
47
- backendNodeId: tuple[DOMBackendNodeId, ...] | None = None
48
- """`Node`'s id, corresponds to DOM.Node.backendNodeId."""
49
- attributes: tuple[ArrayOfStrings, ...] | None = None
50
- # attributes: list[int] | None = None
51
- """Attributes of an `Element` node. Flatten name, value pairs."""
52
- textValue: RareStringData | None = None
53
- """Only set for textarea elements, contains the text value."""
54
- inputValue: RareStringData | None = None
55
- """Only set for input elements, contains the input's associated text value."""
56
- inputChecked: RareBooleanData | None = None
57
- """Only set for radio and checkbox input elements, indicates if the element has been checked"""
58
- optionSelected: RareBooleanData | None = None
59
- """Only set for option elements, indicates if the element has been selected"""
60
- contentDocumentIndex: RareIntegerData | None = None
61
- """The index of the document in the list of the snapshot documents."""
62
- pseudoType: RareStringData | None = None
63
- """Type of a pseudo element node."""
64
- pseudoIdentifier: RareStringData | None = None
65
- """Pseudo element identifier for this node. Only present if there is a valid pseudoType."""
66
- isClickable: RareBooleanData | None = None
67
- """Whether this DOM node responds to mouse clicks. This includes nodes that have had click event listeners attached via JavaScript as well as anchor tags that naturally navigate when clicked."""
68
- currentSourceURL: RareStringData | None = None
69
- """The selected url for nodes with a srcset attribute."""
70
- originURL: RareStringData | None = None
71
- """The url of the script (if any) that generates this node."""
72
-
73
-
74
- class LayoutTreeSnapshot(BaseModel, frozen=True):
75
- """Table of details of an element in the DOM tree with a `LayoutObject`."""
76
-
77
- nodeIndex: tuple[int, ...]
78
- """Index of the corresponding node in the `NodeTreeSnapshot` array returned by `captureSnapshot`."""
79
- styles: tuple[ArrayOfStrings, ...]
80
- """Array of indexes specifying computed style strings, filtered according to the computedStyles parameter passed to captureSnapshot."""
81
- bounds: tuple[Rectangle, ...]
82
- """The absolute position bounding box."""
83
- text: tuple[StringIndex, ...]
84
- """Contents of the `LayoutText`, if any."""
85
- stackingContexts: RareBooleanData
86
- """Stacking context information."""
87
- paintOrders: tuple[int, ...] | None = None
88
- """Global paint order index, which is determined by the stacking order of the nodes. Nodes that are painted together will have the same index. Only provided if `includePaintOrder` in `captureSnapshot` was true."""
89
- offsetRects: tuple[Rectangle, ...] | None = None
90
- """The offset rect of nodes. Only available when `includeDOMRects` is set to true"""
91
- scrollRects: tuple[Rectangle, ...] | None = None
92
- """The scroll rect of nodes. Only available when `includeDOMRects` is set to true"""
93
- clientRects: tuple[Rectangle, ...] | None = None
94
- """The client rect of nodes. Only available when `includeDOMRects` is set to true"""
95
- blendedBackgroundColors: tuple[StringIndex, ...] | None = None
96
- """The list of background colors that are blended with colors of overlapping elements. Experimental"""
97
- textColorOpacities: tuple[float, ...] | None = None
98
- """The list of computed text opacities. Experimental"""
99
-
100
-
101
- class TextBoxSnapshot(BaseModel, frozen=True):
102
- """Table of details of the post layout rendered text positions. The exact layout should not be regarded as stable and may change between versions."""
103
-
104
- layoutIndex: tuple[int, ...]
105
- """Index of the layout tree node that owns this box collection."""
106
- bounds: tuple[Rectangle, ...]
107
- """The absolute position bounding box."""
108
- start: tuple[int, ...]
109
- """The starting index in characters, for this post layout textbox substring. Characters that would be represented as a surrogate pair in UTF-16 have length 2."""
110
- length: tuple[int, ...]
111
- """The number of characters in this post layout textbox substring. Characters that would be represented as a surrogate pair in UTF-16 have length 2."""
112
-
113
-
114
- class DocumentSnapshot(BaseModel, frozen=True):
115
- documentURL: StringIndex
116
- """Document URL that `Document` or `FrameOwner` node points to."""
117
- title: StringIndex
118
- """Document title."""
119
- baseURL: StringIndex
120
- """Base URL that `Document` or `FrameOwner` node uses for URL completion."""
121
- contentLanguage: StringIndex
122
- """Contains the document's content language."""
123
- encodingName: StringIndex
124
- """Contains the document's character set encoding."""
125
- publicId: StringIndex
126
- """`DocumentType` node's publicId."""
127
- systemId: StringIndex
128
- """`DocumentType` node's systemId."""
129
- frameId: StringIndex
130
- """Frame ID for frame owner elements and also for the document node."""
131
- nodes: NodeTreeSnapshot
132
- """A table with dom nodes."""
133
- layout: LayoutTreeSnapshot
134
- """The nodes in the layout tree."""
135
- textBoxes: TextBoxSnapshot
136
- """The post-layout inline text nodes."""
137
- scrollOffsetX: float | None = None
138
- """Horizontal scroll offset."""
139
- scrollOffsetY: float | None = None
140
- """Vertical scroll offset."""
141
- contentWidth: float | None = None
142
- """Document content width."""
143
- contentHeight: float | None = None
144
- """Document content height."""
145
-
146
-
147
- class DOMSnapshot(BaseModel, frozen=True):
148
- documents: tuple[DocumentSnapshot, ...]
149
- """The nodes in the DOM tree. The DOMNode at index 0 corresponds to the root document."""
150
- strings: tuple[str, ...]
151
- """Shared string table that all string properties refer to with indexes."""
152
-
153
-
154
- NodeIndex = NewType("NodeIndex", int)
155
- LayoutIndex = NewType("LayoutIndex", int)
156
-
157
-
158
- class DOMSnapshotContext(TypedDict):
159
- dom_snapshot: DOMSnapshot
160
- node_id_to_node_index: dict[DOMBackendNodeId, NodeIndex]
161
- # Below are dicts to help with non-1:1 index mappings like layouts and RareStringData's
162
- node_index_to_layout_index: dict[NodeIndex, LayoutIndex]
163
- node_index_to_current_src_url_index: dict[NodeIndex, StringIndex]
164
- node_index_to_text_value_index: dict[NodeIndex, StringIndex]
165
-
166
-
167
- def create_snapshot_context(dom_snapshot: DOMSnapshot) -> DOMSnapshotContext:
168
- """
169
- Creates a DOMSnapshotContext that can be used to efficiently extract data from a DOMSnapshot.
170
-
171
- Given the fact that a DOMSnapshot uses long flat correlated lists (see below), this context
172
- enables efficient lookups that avoid full list scans.
173
-
174
- NOTE: Currently, this context and associated code assumes document[0]
175
-
176
- The DOMSnapshot domain represents the DOM tree as as set of objects each containing
177
- parallel flat arrays — NodeTreeSnapshot, LayoutTreeSnapshot, etc.
178
-
179
- Each node in the DOM tree is represented in the NodeTreeSnapshot object by corresponding
180
- entries at the same index across multiple arrays (parentIndex, nodeType, backendNodeId, etc.):
181
-
182
- NodeTreeSnapshot:
183
- │ 0 │ 1 | 2 | 3 | 4
184
- ──────────────┼────────┼────────┼────────┼────────┼────────
185
- actual tag │ <html> │ <body> │ <div> │ <p> │ <span>
186
- parentIndex │ -1 │ 0 │ 1 │ 2 │ 3
187
- backendNodeId │ 666 │ 745 │ 103 │ 421 │ 7
188
- attributes │ ... │ ... │ ... │ ... │ ...
189
- nodeValue │ ... │ ... │ ... │ ... │ ...
190
- etc │ ... │ ... │ ... │ ... │ ...
191
-
192
-
193
- Because layout is independent of node ordering, entries in the LayoutTreeSnapshot object maintain
194
- a reference back to their corresponding node through the nodeIndex field along with layout specific
195
- properties (bounds, paintOrders, etc).
196
-
197
- LayoutTreeSnapshot:
198
- │ 0 │ 1 │ 2 │ 3 │ 4
199
- ──────────────┼────────┼────────┼────────┼────────┼────────
200
- bounds │ ... │ ... │ ... │ ... │ ...
201
- etc │ ... │ ... │ ... │ ... │ ...
202
- nodeIndex │ 3 │ 0 │ 4 │ 2 │ 1
203
- ──────────────┼────────┼────────┼────────┼────────┼────────
204
- │ ↓ │ ↓ │ ↓ │ ↓ │ ↓
205
- backendNodeId │ 421 │ 666 │ 7 │ 103 │ 745
206
- actual tag │ <p> │ <html> │ <span> │ <div> │ <body>
207
- """
208
- document = dom_snapshot.documents[0]
209
- return {
210
- "dom_snapshot": dom_snapshot,
211
- "node_id_to_node_index": {
212
- node_id: index
213
- for index, node_id in enumerate(document.nodes.backendNodeId or [])
214
- },
215
- "node_index_to_layout_index": {
216
- node_index: layout_index
217
- for layout_index, node_index in enumerate(document.layout.nodeIndex)
218
- },
219
- "node_index_to_current_src_url_index": _dict_for_rare_string_data(
220
- document.nodes.currentSourceURL
221
- ),
222
- "node_index_to_text_value_index": _dict_for_rare_string_data(
223
- document.nodes.textValue
224
- ),
225
- }
226
-
227
-
228
- def index_for_node_id(
229
- node_id: DOMBackendNodeId, context: DOMSnapshotContext
230
- ) -> NodeIndex | None:
231
- return context["node_id_to_node_index"].get(node_id, None)
232
-
233
-
234
- def bounds_for_node_index(
235
- node_index: NodeIndex, context: DOMSnapshotContext
236
- ) -> Rectangle | None:
237
- return (
238
- context["dom_snapshot"].documents[0].layout.bounds[layout_index]
239
- if (layout_index := _layout_index_for_node_index(node_index, context))
240
- is not None
241
- else None
242
- )
243
-
244
-
245
- def current_url_src_for_node_index(
246
- node_index: NodeIndex, context: DOMSnapshotContext
247
- ) -> str | None:
248
- return _rare_string_for_node_index(
249
- node_index, "node_index_to_current_src_url_index", context
250
- )
251
-
252
-
253
- def text_value_for_node_index(
254
- node_index: NodeIndex, context: DOMSnapshotContext
255
- ) -> str | None:
256
- return _rare_string_for_node_index(
257
- node_index, "node_index_to_text_value_index", context
258
- )
259
-
260
-
261
- def _layout_index_for_node_index(
262
- node_index: NodeIndex, context: DOMSnapshotContext
263
- ) -> LayoutIndex | None:
264
- """Returns the index of the layout entry, if any, corresponding to the given node id."""
265
- return context["node_index_to_layout_index"].get(node_index, None)
266
-
267
-
268
- def _rare_string_for_node_index(
269
- node_index: NodeIndex,
270
- dict_name: Literal[
271
- "node_index_to_text_value_index", "node_index_to_current_src_url_index"
272
- ],
273
- context: DOMSnapshotContext,
274
- ) -> str | None:
275
- return (
276
- _string_for_string_index(text_value_index, context)
277
- if (text_value_index := context[dict_name].get(node_index, None))
278
- else None
279
- )
280
-
281
-
282
- def _string_for_string_index(index: StringIndex, context: DOMSnapshotContext) -> str:
283
- return context["dom_snapshot"].strings[index]
284
-
285
-
286
- def _dict_for_rare_string_data(
287
- rare_string_data: RareStringData,
288
- ) -> dict[NodeIndex, StringIndex]:
289
- """Creates a dictionary from a RareStringData object enabling lookups given a node index."""
290
- return {
291
- index: value
292
- for index, value in zip(rare_string_data.index, rare_string_data.value)
293
- }
@@ -1,94 +0,0 @@
1
- """
2
- Types associated with Chrome DevTools Protocol's 'Page' Domain
3
-
4
- https://chromedevtools.github.io/devtools-protocol/tot/Page/
5
- """
6
-
7
- from typing import Literal, NewType
8
-
9
- from pydantic import BaseModel
10
-
11
- FrameId = NewType("FrameId", str)
12
-
13
-
14
- class SecurityOriginDetails(BaseModel, frozen=True):
15
- """Additional information about the frame document's security origin."""
16
-
17
- isLocalhost: bool
18
- """Indicates whether the frame document's security origin is one of the local hostnames (e.g. "localhost") or IP addresses (IPv4 127.0.0.0/8 or IPv6 ::1)."""
19
-
20
-
21
- AdFrameType = Literal["none", "child", "root"]
22
-
23
- AdFrameExplanation = Literal["ParentIsAd", "CreatedByAdScript", "MatchedBlockingRule"]
24
-
25
-
26
- class AdFrameStatus(BaseModel, frozen=True):
27
- """Indicates whether a frame has been identified as an ad and why."""
28
-
29
- adFrameType: AdFrameType
30
- explanations: tuple[AdFrameExplanation, ...] | None = None
31
-
32
-
33
- SecureContextType = Literal[
34
- "Secure", "SecureLocalhost", "InsecureScheme", "InsecureAncestor"
35
- ]
36
-
37
-
38
- CrossOriginIsolatedContextType = Literal[
39
- "Isolated", "NotIsolated", "NotIsolatedFeatureDisabled"
40
- ]
41
-
42
-
43
- GatedAPIFeatures = Literal[
44
- "SharedArrayBuffers",
45
- "SharedArrayBuffersTransferAllowed",
46
- "PerformanceMeasureMemory",
47
- "PerformanceProfile",
48
- ]
49
-
50
-
51
- class Frame(BaseModel, frozen=True):
52
- """Information about the Frame on the page."""
53
-
54
- id: FrameId
55
- """Frame unique identifier."""
56
- parentId: FrameId | None = None
57
- """Parent frame identifier."""
58
- loaderId: object # Network.LoaderId
59
- """Identifier of the loader associated with this frame."""
60
- name: str | None = None
61
- """Frame's name as specified in the tag."""
62
- url: str
63
- """Frame document's URL without fragment."""
64
- urlFragment: str | None = None
65
- """Frame document's URL fragment including the '#'."""
66
- domainAndRegistry: str
67
- """Frame document's registered domain, taking the public suffixes list into account. Extracted from the Frame's url. Example URLs: http://www.google.com/file.html -> "google.com" http://a.b.co.uk/file.html -> "b.co.uk"""
68
- securityOrigin: str
69
- """Frame document's security origin."""
70
- securityOriginDetails: SecurityOriginDetails | None = None
71
- """Additional details about the frame document's security origin."""
72
- mimeType: str
73
- """Frame document's mimeType as determined by the browser."""
74
- unreachableUrl: str | None = None
75
- """If the frame failed to load, this contains the URL that could not be loaded. Note that unlike url above, this URL may contain a fragment."""
76
- adFrameStatus: AdFrameStatus | None = None
77
- """Indicates whether this frame was tagged as an ad and why."""
78
- secureContextType: SecureContextType
79
- """Indicates whether the main document is a secure context and explains why that is the case."""
80
- crossOriginIsolatedContextType: CrossOriginIsolatedContextType
81
- """Indicates whether this is a cross origin isolated context."""
82
- gatedAPIFeatures: tuple[GatedAPIFeatures, ...]
83
- """Indicated which gated APIs / features are available."""
84
-
85
-
86
- class FrameTree(BaseModel, frozen=True):
87
- frame: Frame
88
- """Frame information for this tree item."""
89
- childFrames: tuple["FrameTree", ...] | None = None
90
- """Child frames."""
91
-
92
-
93
- class FrameTrees(BaseModel, frozen=True):
94
- frameTree: FrameTree
@@ -1,2 +0,0 @@
1
- SERVER_PORT = 5555
2
- DEFAULT_SESSION_NAME = "WebBrowser"
@@ -1,2 +0,0 @@
1
- <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1151.9489726043614 684.1200665344755" width="1151.9489726043614" height="684.1200665344755" class="excalidraw-svg"><!-- svg-source:excalidraw --><metadata><!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1da1PbyFx1MDAxMv2eX+Fiv9xbXHUwMDE1tNPT89xPl+eFkFx1MDAxMFx1MDAxMshcdTAwMDLZbG3JtoxcdTAwMDXGNn7wyFb+++1cdTAwMTFgyZIsP1x1MDAxMIG9kGxlXHUwMDEzzUhqzXSfc3pe/P2mUllcdTAwMWHcdIOl3ypLwXXNb4X1nn+19NZdv1xmev2w06ZcIlx1MDAxZf2731x1MDAxOfZqUU2/2/3t11871X5YXHUwMDBm/bZ3Xr+9IWhcdTAwMDXnQXvQpyp/0L8rlb+jP6kkrLvbVqu7XHUwMDFierDVvj5a8Vt8s1x1MDAxZWxeXHUwMDFk8+jWqNK9XHUwMDFkvaA28NsnrSAuuqbry0JzXHUwMDBmXHUwMDE4/UYrjJSMj4pvXFwxR+VcdTAwMDFyXHUwMDA1QmpulEAzKr9cbuuDJtVcdTAwMDFA8Ohmq7liXHUwMDAyqeqoSjNcYk+aXHUwMDAzqiO09SRVofdcYmnBKjmqc2vUb1x1MDAxNTa60lx1MDAxZvQ6Z8Fap9XpOct/gcD9ju2u+rWzk15n2K6P6lxmen673/V71FBxvUbYau1cdTAwMGZuoqdTO1MvLKXecXj3XHQ8dX3SXfTSk2Y76PfH7O10/Vo4cM1cdTAwMDUsvuos7G7Xo377M7ap559cdTAwMDfbruPaw1ZrdDls14PryFx0+Njb2vW7t913etyjeHflR2x7XHUwMDEwuFx1MDAwNytqY7RcdTAwMThbXHUwMDEyu5xMdN/d1d1OO3I/tFppbbmKbeqvk+9ccqJnNvxWP4g7wFx1MDAxObaR9sukb4653iC4jnsl4bnr29ubXHUwMDE2Ts6WRkU/7v5cdTAwMTa317Bb929NXHUwMDAwLVx1MDAxOEfJQYLGUXkrbJ+lXHUwMDFis9WpncVWv0k0Uip0Rlx1MDAwNmSiZczkKFCQeVx1MDAxY9zr6e1cdTAwMDKYUSZcdTAwMWQoMDVQuPZcdTAwMTCspc9wgWIxXHUwMDFiJ/w1LibEXHUwMDA15sfFWO1RXHUwMDAwXGJcdTAwMTBK5fg/aj3J/0FcdTAwMTM+cU7/LVx1MDAxMlx1MDAwMGU6bOyMzlx06du/9INe5YNfa4btRG9cdTAwMTOjXHUwMDFjXHUwMDE0Vmh02oP98Lv7OM7Grm7652HL9YtcdTAwMWN710orPHGttFSjz1xuekvJplx1MDAxYYREY6NcboNONy6t0Vx1MDAxM316c297XHUwMDE2Pur0wpOw7beKbfeHg87noH9r/aA3XGaS7Vx1MDAxN2zdh1x1MDAwYnhcXFx1MDAxNsT3p+NLeX067OxcdTAwMGWPvrdbwfuj37Hbm4NcdTAwMWGZ9ZApxZQhxlx1MDAxMjzmtSjiXHUwMDAxmKc1l0xJTpDLIG7N+4jngnvaWK4lXG4tNf0vXHUwMDFi8iDAU1xcSis0MrohQZ+lQMAvvqybRuM5hn/MaV/58eD96coh9i5qO83TZvD1XWPlnlx1MDAxN+ZAXHTxYPZcdTAwMDRjOKJcdTAwMTImXHUwMDBmP1x1MDAwMFx1MDAxMSZcdTAwMDFcYtdMaKZcdTAwMTPd9+hcdTAwMDR62fjdXGbD041cdTAwMDSBvs1/0m3989PNzWFjb79cdTAwMDH7O/ZqQ1ZxXHUwMDA1zfiD71/p93qdq/mIWVxiQJ2M08WJefRhM1x1MDAxMPMycumR6rQopVaEtTpcdTAwMTWmTE5cdTAwMGJTLTxjrUXibaJwXHUwMDAzPFx1MDAxYqUvjJjLj0w5O39cdTAwMTNhMmY1ZsVqlG1AJjJjXHUwMDBlXHUwMDA3JSnJWCxcdTAwMDZcdTAwMGJ8m0RcdTAwMWRpvodw+Db1NtFMLn1nyp6IuafQZZq5M2aXQ9pcdTAwMWLb8OVD6+vpJlx1MDAxYoR4XHUwMDA14c7h9lxyzk7alMd4UnLLXHUwMDA0M8hI0sUtXHUwMDE1wYGUVMzQSqYp4+EsdpVcdTAwMTFpg4NcdTAwMDOUjJQ8XHRJncPZKu6Vcji6XHUwMDEx/XpcdTAwMTlIoGZHXHUwMDAyNMyCYZCXy4JkXHUwMDEzk1lQpKUkUlx1MDAxZv48Mj4+t43q3uHBrGT85WRQW/1+svZBrFx1MDAxZJzwU9g24ceV0shYXHUwMDExgEIy/lx1MDAxNifj0YfNRMaKeSSYmWVaMVx1MDAwM1x1MDAwNtPhx6aFXHUwMDFmUbRHZGzcYIRVRsts+MmSw+/FkbGeg4yZRVJVJsGpST3MJ+phIDlmSXTJ50fG/1x1MDAxOXQ6rW/tq6D6V5VcIosy0H/9O5eZo4qVXHT1noilp/BjmqVcdTAwMGI/oVx1MDAxY8YuTitSKDZcdTAwMGVcdTAwMTfcXG5PXHUwMDEyUysuUFx1MDAxM1x1MDAxZaS1O6CnLFx1MDAwM03ZN1x1MDAxN1xu40i7R4u44eOEWlx1MDAxMX9cdTAwMWKGwiq09NuUnFA/c7SYXHUwMDAzXHUwMDA2TD5cZmSzZX53JZstXHUwMDEzy3CpVD468ILhNlx1MDAwYlxcXHUwMDE5xn8mQ1x1MDAxZqi9le94czpcdTAwMTeTklxiRFxcXGJtup0wbe5cdTAwMWZcdKNY0kI2+vufb6fXXs66eHx/5pNafn+w1jk/XHUwMDBmXHUwMDA39GF7zqhcZtRcdTAwMGb83mCV/FwibJ+M9//d5NMs+UGEfbWh++Bl5jEpSFx1MDAwNlx1MDAxMFRcdTAwMWKFxjKbqHXid51LeZbCWkWj4ZyEepx0R9F0PW5qxv+Cdn26wZfVj6p9IJdPL9vQqF7uc32UXHUwMDE4ZExcdTAwMThM9lx1MDAxMpUx66hOKG2MXHUwMDExXCJjMcxlYNSmK1x1MDAwZfKagZ+JQTI/WZbGxqBV7VxczSTORj49kzjj4EmKPCFIXHUwMDFkU3IjU3BLLUBwK1x1MDAxNedIToXJycB7vKX0yYC12oJlhiuRSMZf6ljJXHUwMDFjgGtn113coSdcdTAwMDVcYuZcIutE1YWgOVx1MDAwM1x1MDAxMFx1MDAwYk3jXHUwMDE1waAgXHUwMDE0XHUwMDFjRfFcIqKrOVx1MDAxOHT7uSorVfKIuuo8rNeTI1x1MDAwNilpNUXIpKVVyu5yxNTq4LAx3Fxc391W5+y6sbq90v/cWZt5+INbj6FmXHUwMDFjyG+EtVwi9pNbPaUowIWO5Fx1MDAxNFquRXaSUlruXHUwMDExQCglXGYwQVx1MDAwMj5cdTAwMWLfqMFzXHUwMDEzXCLuNUpcdTAwMTn5svRVTFx1MDAwMuzT0fFlz3xcdTAwMWVemsN9/8rU2OHV1Vx1MDAwMtnYyqwybOKkXHUwMDA19Vx1MDAxMzFoMupjrFx1MDAxMIKnr96DhaC7qJvVT5zzb5/pXHUwMDBmq9vhylxcXHUwMDFhzI0liJJGM0ZcdTAwMDbMQJhcXIBH6kWA0Vx1MDAwMrTOzPk7XHUwMDE5NiWcQGlPoDWaSSstvs4slFx1MDAxZj6rs5OqVFpYkHnzXG5cXMhJYcJcdTAwMTFcdTAwMWPSlT6rMLdbZzh13Vx1MDAxNfcqa/c0lkuvkys91VxugWKKS9PsZPvLYdxO53N7a+P6uHbx6ejdUfM97lh1PDPjolx1MDAxNlx1MDAxZeXAmpFnMctlrJHuRjDAU9pcdTAwMWFkympSzDlDXHUwMDE4XHUwMDFjXFz6YYxiJL2NXHUwMDA0mbN+jnJlXHUwMDBmSNpcdTAwMTGEWMqaTTwnUc78Q5U3eLX6MvBi7eF0q6REUDxDrFRmcPL0pLRW0p2LLTFajG+H9f6nz5fBfGNcdTAwMWUyXHUwMDFhnSmHb0dcdTAwMDbMwLdcdTAwMDK4R3lcdTAwMGZYpIhRaFJrUYGpabFcdTAwMDQoKZYsJfAoQFDU5MxcdTAwMWW8XHUwMDEy7lx1MDAwM1x1MDAwM2h9dsLVXHUwMDFhjVwi/sybwNMsc3WUxXLr1klas0igXHUwMDE0OraRiSmnXHUwMDA1XHUwMDE411xypPeDXHUwMDFl2et1b3LpdkKNJ+LaKeSW5tpcdMaXQ7S1xtlaf+f31uF6/6vZOTSbLf86R4dPXCJaKzxrgUBcdTAwMDApt2Upnlx1MDAxNdJcdTAwMTOU9KJynaytjGVVrMStJ1x1MDAxNKfbuVwigJA8XHUwMDA3XHUwMDFhXuS8flmwsDE7LFx1MDAwMOVCwDilVDm4QFnVRClcdTAwMGUgJaksxUqk0FvnfH+8/mnj8HCYP1x1MDAwM1x1MDAxZoV/TKFvi560tdrfYltqcPTlUp33m9f90F/eyH/s/Fx1MDAxM/uS8lx1MDAxNDNcdTAwMGaCXHUwMDE1xGPmk4uomTOPW05WKLBMSjVcdTAwMWV+XFx7dkr4gfFcZlx0IK5cYvelTc78vzJzWSG4OVx1MDAwNzOTXHUwMDFmXHUwMDExXHUwMDE45i6TXHUwMDA3YScureGUpUgh9ELzdmU6doaa3XBsxZFXWMtfJp9f4emGm6fwYd5wc9b8cqj5xH9nVju1L19u9mpcdTAwMWNcdTAwMGZ2r1x1MDAwZj6vrJdDzVx1MDAxYTyrmKT0mHBcdTAwMDFVjmZ/JebHRYX/zoFcbkpoUt75XHUwMDBi7nT2cpzauuHnsV1nj57adjdcdTAwMGZrovlufVZevrlcdTAwMThe1C7+svi+1tzdOVvfap6Y/dJ4XHUwMDE5hUpcZrQ/iJdHXHUwMDFmNlx1MDAxMy9cdTAwMWK33JU4l+KLWTs+o2tcXGRcdTAwMTXGXHUwMDFl41x1MDAxZVwidbiyQkiGXHRB9srKZcXf1lx1MDAxY8KYc2uoP3J3b1x1MDAwMnVkQVx1MDAwMHKjlS57jHpuv87Q8l7Lv7nqRb6UR8p5xU9HyVN4ME3JecaXQ8jHXHUwMDFm19pcdTAwMWLrq2ts46/9LXZZXHUwMDA3ubVTnZmQlfFcdTAwMTi5XHUwMDEywVx1MDAwMicpx1hqXHUwMDFhWFwiaXmlXHJIRM21zFmEq5hcdTAwMDdotdvvaqTKXHUwMDEz61x1MDAxYT2pXHUwMDE1N25Dt7Ral71trc6qL4aht1x1MDAxZjwkTVx1MDAwNGA0XHUwMDEzLHduS1mbvnqPXHUwMDFiXHUwMDE2XHUwMDA1MqbL5O1bXHUwMDA3lpdX68OrxnY+wf6kfLrwuY+6XHUwMDAwXHUwMDFm0ZiyXHUwMDE24GeaskBcdTAwMGZcdTAwMTj0tNtDqq3bzS9Mas+qW1x1MDAxZsKMXHUwMDEyzDC03CidjXxcdTAwMTJcdTAwMTRukN1a0uPEQ/p1gVf58f5udkVgQUvUNneBrZJcdTAwMTNcdTAwMDU5N5ZcdTAwMTk0uuxcdTAwMTF0UJDEmFx1MDAwNUfQa62Q3lU0gp5T4+lUwVx1MDAxNDLOXHUwMDFiRM+xv1x1MDAxY2HQv/h0cvbfz9368c3FatO3vn2/488jXGasJGGghFx1MDAwMqmSh1x1MDAxZETNXHUwMDA3lEtQMoCSa6BMMG85y1RdXHUwMDAwTFDSYSxHelxmd6fClCxcZlx1MDAxYY26SS5d/39cdTAwMDaKnYdcdTAwMGJcdTAwMDPOjLCE5nmT1Sp7dbQ2XGZcclx1MDAwN5Os8OhcdP3uoCGHXHK4eVx1MDAxNlx0PUpGPlxcXHUwMDBlgY8+bFx1MDAwNlx1MDAwMo+2qytcdTAwMGXILONgXHUwMDE1XHUwMDFiX3OmXHUwMDE0SXeBXHUwMDE0gMwtyFVZ6W6UW+NNdTiSrFx1MDAxM8pmQ1S/8vfDwvL9XHUwMDFjXHUwMDE5vVKgKD/KUrXrTZwozFx0oyWpsLL521xy+eOD1pxtXHUwMDA1PnFlv/+tvdbsdc7D4fm39urt1rJcXDa/r1+5r17J1H46Zp/Cpmlmn+FbymH54pynaEtcdTAwMWQniVx1MDAwZlx1MDAwNFx1MDAwMFx1MDAxYY1AKZhIL6KRnpJcdTAwMDY1aJBcZlCZPJJcdTAwMTeeQZJcdTAwMDFcXFh30JXMS1x1MDAwMTzK+oVwi3VcdTAwMTRcYnd43CukPFxiUj7MyvST9+JJhVx1MDAwNlx1MDAwNMtcdTAwMGVcdTAwMTJGSFMwp25cdTAwMWOlWPtcdTAwMTNPrjn9Lj6yVT7fMnBiZCFcdTAwMTdcdTAwMWGLLHUr3uTwcL+ygVx1MDAxMT8v84mlbc0rzlAq41vzXHUwMDAw3EE4mmLfLbVcdTAwMTDxqvXKYlvdZtqLVzzZOW4heSS4QVxuyjUpiVx1MDAwNczuXHUwMDFlfJZ78UZOPYPM45p7yClkhVCaa5FcdTAwMWWnUZ5RjFIwy7S2UmdnbrTxnKyXwFx1MDAwNVxuxlx1MDAxMlv5XodpysLk3XlknmYgXHLknlk0eVx1MDAxYrR2p75cdTAwMDFbbFx1MDAwN86jQOW9zDvtd9qVXreWq+myhU8n4aZIpbSEy5pejmIrTkaLXHUwMDE0XHUwMDFiWlx1MDAxN8qSmFx1MDAwNCmaU6dcZrpcdFryXHUwMDEwxYQkLDRcdEeJ1Vx1MDAxYd3O0Fx1MDAxOKs1MCsxZ/nESz0vpSwg+PhgccZcdTAwMTknNFx1MDAxN9bkXHUwMDBlw+jJ4ky50yNcdTAwMDUsdk5CXHUwMDExQlx1MDAxMD6YhTb0liqmlie7b+r+x1x1MDAxM0/Fk74p8cSlZsxcdTAwMWHhXHUwMDAyXHUwMDEyXHUwMDEwOSbqPZp4Kk5T01x1MDAxNjJ0o7tWKyRYSVx1MDAxY0j5vNVT8XRcXOEpMiRcdTAwMTcpqbWUwrhBO5bcN3Cb86LnXCJcdTAwMGa5va2Rs1x1MDAxMIZcdTAwMGI37Vx1MDAwNYpcdTAwMDN3U3fxXGJJjKEk7ImoKYZRuIM3Slx1MDAxZdZ+5pg6XHUwMDA3WO49PJOl3IQxUsV5i1Mp9FxuMtnoMKHFRs1G9s2VyeL1hVx1MDAxY/avz+c8nk1bvtCZ0KWC7ySnj25Ou/vPQOLis61cdTAwMTI4x9y+bNJLym3MJo8wXHUwMDBmXHUwMDA1uZlgeJ4sm4RcdTAwMWI1oLFgXGZcdTAwMTPqn3KczMibZ0hhl1x1MDAwMaTnTvhcdTAwMTPE3FaMzVx1MDAwNt4msdLTjLtlKZJb7Vx1MDAwZW7Lild6RLTgVCsp3Fx1MDAxMuLX/XpzIO2nOfJTI8lcdTAwMTEpWnJ33Ew+SNNcdTAwMWTByKVZbGywaFx1MDAxZFx1MDAwMYUte9A6gvrttvDgOsjPUXPLny5NnVwib9Jpaq715WSqxadixT+4ptVcbrv9zPG6SnjUdU5JIeWlNn28rtsnpFx1MDAxOWrN3PAnSdFsyFx1MDAwYveDMizltFx1MDAwMpDQUeSEfEGdctZcdTAwMTBcdTAwMDSqUWvIf3j4XHUwMDFmPFxcaCHXilx1MDAwNHPuTnZcdTAwMGVcdTAwMTMnJ91UXHUwMDAzSJ48JP/RhdZF82Zv87z+4VlcdTAwMWN2L4zboD5cdTAwMDd8XHUwMDE1xOPow2bhXFyk+DKkfaJcdTAwMTNJKNfBXHUwMDE056KWXHUwMDFlXUSrXHUwMDE0XHUwMDE4zZMrxkcnPFx0lyxcdTAwMTEhg1x1MDAxMFx1MDAxMiFvu03ZXHUwMDA37D7jNTtzxNuX2elWKMI/wq/8XHUwMDEz7CefNMOsO4aQQdnT/u6HXHUwMDEyyVx1MDAwN037f+jUg9a39sredi7bRsWVsdKn49opXGaX5tpcdTAwMWPb52baN3dw4X6K2/6AXHUwMDFhf1x1MDAwNMTkbWE91Vx1MDAwZbfXXHUwMDA2QTdug+iSs2Sj7Vdb6Y5Yulxmg6vVyfvY3tyBi1x1MDAwYqwgXCKBXHUwMDFmb378XHUwMDBm7mQwXHUwMDE2In0=<!-- payload-end --></metadata><defs><style class="style-fonts">
2
- @font-face { font-family: Excalifont; src: url(data:font/woff2;base64,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); }</style></defs><rect x="0" y="0" width="1151.9489726043614" height="684.1200665344755" fill="#ffffff"></rect><g stroke-linecap="round" transform="translate(10 194.5711563885559) rotate(0 565.9744863021807 239.77445507295982)"><path d="M32 0 C440.26 0, 848.52 0, 1099.95 0 M32 0 C277.05 0, 522.1 0, 1099.95 0 M1099.95 0 C1121.28 0, 1131.95 10.67, 1131.95 32 M1099.95 0 C1121.28 0, 1131.95 10.67, 1131.95 32 M1131.95 32 C1131.95 150.62, 1131.95 269.25, 1131.95 447.55 M1131.95 32 C1131.95 185.13, 1131.95 338.25, 1131.95 447.55 M1131.95 447.55 C1131.95 468.88, 1121.28 479.55, 1099.95 479.55 M1131.95 447.55 C1131.95 468.88, 1121.28 479.55, 1099.95 479.55 M1099.95 479.55 C748.24 479.55, 396.52 479.55, 32 479.55 M1099.95 479.55 C834.42 479.55, 568.89 479.55, 32 479.55 M32 479.55 C10.67 479.55, 0 468.88, 0 447.55 M32 479.55 C10.67 479.55, 0 468.88, 0 447.55 M0 447.55 C0 312.26, 0 176.97, 0 32 M0 447.55 C0 300.27, 0 152.99, 0 32 M0 32 C0 10.67, 10.67 0, 32 0 M0 32 C0 10.67, 10.67 0, 32 0" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(512.3145351219611 199.5711563885559) rotate(0 63.65995118021965 12.5)"><text x="63.65995118021965" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">User Machine</text></g><g stroke-linecap="round" transform="translate(72.79440712890778 319.9312644412801) rotate(0 121.39463767378876 70.81279736512107)"><path d="M32 0 C83.4 0, 134.8 0, 210.79 0 C232.12 0, 242.79 10.67, 242.79 32 C242.79 54.49, 242.79 76.98, 242.79 109.63 C242.79 130.96, 232.12 141.63, 210.79 141.63 C156.76 141.63, 102.72 141.63, 32 141.63 C10.67 141.63, 0 130.96, 0 109.63 C0 91.26, 0 72.9, 0 32 C0 10.67, 10.67 0, 32 0" stroke="none" stroke-width="0" fill="#a5d8ff"></path><path d="M32 0 C69.15 0, 106.31 0, 210.79 0 M32 0 C69.22 0, 106.43 0, 210.79 0 M210.79 0 C232.12 0, 242.79 10.67, 242.79 32 M210.79 0 C232.12 0, 242.79 10.67, 242.79 32 M242.79 32 C242.79 59.47, 242.79 86.93, 242.79 109.63 M242.79 32 C242.79 57.81, 242.79 83.63, 242.79 109.63 M242.79 109.63 C242.79 130.96, 232.12 141.63, 210.79 141.63 M242.79 109.63 C242.79 130.96, 232.12 141.63, 210.79 141.63 M210.79 141.63 C150.82 141.63, 90.85 141.63, 32 141.63 M210.79 141.63 C162.02 141.63, 113.25 141.63, 32 141.63 M32 141.63 C10.67 141.63, 0 130.96, 0 109.63 M32 141.63 C10.67 141.63, 0 130.96, 0 109.63 M0 109.63 C0 79.86, 0 50.09, 0 32 M0 109.63 C0 90.44, 0 71.25, 0 32 M0 32 C0 10.67, 10.67 0, 32 0 M0 32 C0 10.67, 10.67 0, 32 0" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(156.7390783720325 324.9312644412801) rotate(0 37.44996643066406 12.5)"><text x="37.44996643066406" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Inspect</text></g><g stroke-linecap="round" transform="translate(84.54810986550194 375.15337588165823) rotate(0 107.44675228707365 30)"><path d="M0 0 L214.89 0 L214.89 60 L0 60" stroke="none" stroke-width="0" fill="#ffffff"></path><path d="M0 0 C53.75 0, 107.5 0, 214.89 0 M0 0 C68.76 0, 137.53 0, 214.89 0 M214.89 0 C214.89 15.05, 214.89 30.11, 214.89 60 M214.89 0 C214.89 14.27, 214.89 28.53, 214.89 60 M214.89 60 C165.27 60, 115.65 60, 0 60 M214.89 60 C159.4 60, 103.9 60, 0 60 M0 60 C0 44.54, 0 29.09, 0 0 M0 60 C0 44.31, 0 28.63, 0 0" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(121.49492318773184 380.15337588165823) rotate(0 70.49993896484375 25)"><text x="70.49993896484375" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">@tool</text><text x="70.49993896484375" y="42.62" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">web_browser()</text></g><g mask="url(#mask-mjFFufPSf1SK9wE5b3A38)" stroke-linecap="round"><g transform="translate(187.56015151078748 317.0135945389571) rotate(0 0 -82.44017481969692)"><path d="M0 0 C0 -27.48, 0 -137.4, 0 -164.88 M0 0 C0 -27.48, 0 -137.4, 0 -164.88" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(187.56015151078748 317.0135945389571) rotate(0 0 -82.44017481969692)"><path d="M8.55 -141.39 C5.51 -149.75, 2.46 -158.11, 0 -164.88 M8.55 -141.39 C5.63 -149.41, 2.71 -157.43, 0 -164.88" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(187.56015151078748 317.0135945389571) rotate(0 0 -82.44017481969692)"><path d="M-8.55 -141.39 C-5.51 -149.75, -2.46 -158.11, 0 -164.88 M-8.55 -141.39 C-5.63 -149.41, -2.71 -157.43, 0 -164.88" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g></g><mask id="mask-mjFFufPSf1SK9wE5b3A38"><rect x="0" y="0" fill="#fff" width="287.5601515107875" height="581.8939441783509"></rect><rect x="159.65016191537427" y="222.07341971926016" fill="#000" width="55.819979190826416" height="25" opacity="1"></rect></mask><g transform="translate(159.65016191537427 222.07341971926016) rotate(0 27.909989595413208 12.5)"><text x="27.909989595413208" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">https</text></g><g stroke-linecap="round" transform="translate(511.1380355830496 261.0560067374928) rotate(0 296.0723324052453 185.5982010833427)"><path d="M32 0 C149.62 0, 267.24 0, 560.14 0 M32 0 C187.09 0, 342.18 0, 560.14 0 M560.14 0 C581.48 0, 592.14 10.67, 592.14 32 M560.14 0 C581.48 0, 592.14 10.67, 592.14 32 M592.14 32 C592.14 106.07, 592.14 180.14, 592.14 339.2 M592.14 32 C592.14 119.37, 592.14 206.75, 592.14 339.2 M592.14 339.2 C592.14 360.53, 581.48 371.2, 560.14 371.2 M592.14 339.2 C592.14 360.53, 581.48 371.2, 560.14 371.2 M560.14 371.2 C378.98 371.2, 197.81 371.2, 32 371.2 M560.14 371.2 C414.46 371.2, 268.78 371.2, 32 371.2 M32 371.2 C10.67 371.2, 0 360.53, 0 339.2 M32 371.2 C10.67 371.2, 0 360.53, 0 339.2 M0 339.2 C0 241.91, 0 144.63, 0 32 M0 339.2 C0 231.42, 0 123.64, 0 32 M0 32 C0 10.67, 10.67 0, 32 0 M0 32 C0 10.67, 10.67 0, 32 0" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(723.4904326903288 266.0560067374928) rotate(0 83.719935297966 12.5)"><text x="83.719935297966" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Docker Container</text></g><g stroke-linecap="round" transform="translate(856.813721439476 319.02394026433797) rotate(0 105.00943047842574 134.05862969901415)"><path d="M32 0 C86.15 0, 140.29 0, 178.02 0 C199.35 0, 210.02 10.67, 210.02 32 C210.02 106.31, 210.02 180.62, 210.02 236.12 C210.02 257.45, 199.35 268.12, 178.02 268.12 C148.78 268.12, 119.55 268.12, 32 268.12 C10.67 268.12, 0 257.45, 0 236.12 C0 180.91, 0 125.69, 0 32 C0 10.67, 10.67 0, 32 0" stroke="none" stroke-width="0" fill="#b2f2bb"></path><path d="M32 0 C66.75 0, 101.51 0, 178.02 0 M32 0 C80.59 0, 129.17 0, 178.02 0 M178.02 0 C199.35 0, 210.02 10.67, 210.02 32 M178.02 0 C199.35 0, 210.02 10.67, 210.02 32 M210.02 32 C210.02 81.92, 210.02 131.85, 210.02 236.12 M210.02 32 C210.02 96.51, 210.02 161.02, 210.02 236.12 M210.02 236.12 C210.02 257.45, 199.35 268.12, 178.02 268.12 M210.02 236.12 C210.02 257.45, 199.35 268.12, 178.02 268.12 M178.02 268.12 C148.2 268.12, 118.39 268.12, 32 268.12 M178.02 268.12 C133.28 268.12, 88.54 268.12, 32 268.12 M32 268.12 C10.67 268.12, 0 257.45, 0 236.12 M32 268.12 C10.67 268.12, 0 257.45, 0 236.12 M0 236.12 C0 182.7, 0 129.28, 0 32 M0 236.12 C0 157.95, 0 79.78, 0 32 M0 32 C0 10.67, 10.67 0, 32 0 M0 32 C0 10.67, 10.67 0, 32 0" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(894.3132077471885 324.02394026433797) rotate(0 67.50994417071345 12.5)"><text x="67.50994417071342" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">web_server.py</text></g><g stroke-linecap="round" transform="translate(877.0921202829592 385.28743460624077) rotate(0 84.73103163494261 30)"><path d="M0 0 L169.46 0 L169.46 60 L0 60" stroke="none" stroke-width="0" fill="#ffffff"></path><path d="M0 0 C53.78 0, 107.57 0, 169.46 0 M0 0 C52.33 0, 104.66 0, 169.46 0 M169.46 0 C169.46 18.81, 169.46 37.62, 169.46 60 M169.46 0 C169.46 16.96, 169.46 33.91, 169.46 60 M169.46 60 C117.09 60, 64.72 60, 0 60 M169.46 60 C103.78 60, 38.09 60, 0 60 M0 60 C0 37.64, 0 15.28, 0 0 M0 60 C0 40.17, 0 20.35, 0 0" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(902.3931885676058 402.78743460624077) rotate(0 59.42996335029602 12.5)"><text x="59.42996335029602" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">http service</text></g><g stroke-linecap="round" transform="translate(877.0921202829592 502.6643459409565) rotate(0 84.73103163494261 30)"><path d="M0 0 L169.46 0 L169.46 60 L0 60" stroke="none" stroke-width="0" fill="#ffffff"></path><path d="M0 0 C44.91 0, 89.83 0, 169.46 0 M0 0 C48.46 0, 96.93 0, 169.46 0 M169.46 0 C169.46 17.32, 169.46 34.64, 169.46 60 M169.46 0 C169.46 17.54, 169.46 35.09, 169.46 60 M169.46 60 C115.49 60, 61.52 60, 0 60 M169.46 60 C126.2 60, 82.93 60, 0 60 M0 60 C0 40.68, 0 21.36, 0 0 M0 60 C0 46.57, 0 33.14, 0 0" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(910.6531934456499 520.1643459409565) rotate(0 51.16995847225189 12.5)"><text x="51.16995847225189" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Playwright</text></g><g stroke-linecap="round" transform="translate(550.1132356735302 377.40698942769274) rotate(0 80.0698760592802 36.78814072979887)"><path d="M18.39 0 C52.31 0, 86.22 0, 141.75 0 C154.01 0, 160.14 6.13, 160.14 18.39 C160.14 31.12, 160.14 43.85, 160.14 55.18 C160.14 67.44, 154.01 73.58, 141.75 73.58 C105.91 73.58, 70.07 73.58, 18.39 73.58 C6.13 73.58, 0 67.44, 0 55.18 C0 42.96, 0 30.73, 0 18.39 C0 6.13, 6.13 0, 18.39 0" stroke="none" stroke-width="0" fill="#d0bfff"></path><path d="M18.39 0 C46.88 0, 75.37 0, 141.75 0 M18.39 0 C64.75 0, 111.1 0, 141.75 0 M141.75 0 C154.01 0, 160.14 6.13, 160.14 18.39 M141.75 0 C154.01 0, 160.14 6.13, 160.14 18.39 M160.14 18.39 C160.14 32.43, 160.14 46.47, 160.14 55.18 M160.14 18.39 C160.14 30.05, 160.14 41.71, 160.14 55.18 M160.14 55.18 C160.14 67.44, 154.01 73.58, 141.75 73.58 M160.14 55.18 C160.14 67.44, 154.01 73.58, 141.75 73.58 M141.75 73.58 C108.04 73.58, 74.33 73.58, 18.39 73.58 M141.75 73.58 C114.19 73.58, 86.62 73.58, 18.39 73.58 M18.39 73.58 C6.13 73.58, 0 67.44, 0 55.18 M18.39 73.58 C6.13 73.58, 0 67.44, 0 55.18 M0 55.18 C0 41.45, 0 27.72, 0 18.39 M0 55.18 C0 43.04, 0 30.89, 0 18.39 M0 18.39 C0 6.13, 6.13 0, 18.39 0 M0 18.39 C0 6.13, 6.13 0, 18.39 0" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(565.843161916345 401.6951301574916) rotate(0 64.33994981646538 12.5)"><text x="64.33994981646538" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">web_client.py</text></g><g stroke-linecap="round" transform="translate(551.0532404101184 482.26368448858506) rotate(0 80.0698760592802 52.23446167614486)"><path d="M26.12 0 C58.82 0, 91.53 0, 134.02 0 C151.43 0, 160.14 8.71, 160.14 26.12 C160.14 40.3, 160.14 54.49, 160.14 78.35 C160.14 95.76, 151.43 104.47, 134.02 104.47 C103.97 104.47, 73.91 104.47, 26.12 104.47 C8.71 104.47, 0 95.76, 0 78.35 C0 60.51, 0 42.68, 0 26.12 C0 8.71, 8.71 0, 26.12 0" stroke="none" stroke-width="0" fill="#ffd8a8"></path><path d="M26.12 0 C60.32 0, 94.52 0, 134.02 0 M26.12 0 C57.47 0, 88.83 0, 134.02 0 M134.02 0 C151.43 0, 160.14 8.71, 160.14 26.12 M134.02 0 C151.43 0, 160.14 8.71, 160.14 26.12 M160.14 26.12 C160.14 42.25, 160.14 58.38, 160.14 78.35 M160.14 26.12 C160.14 42.48, 160.14 58.85, 160.14 78.35 M160.14 78.35 C160.14 95.76, 151.43 104.47, 134.02 104.47 M160.14 78.35 C160.14 95.76, 151.43 104.47, 134.02 104.47 M134.02 104.47 C106.99 104.47, 79.96 104.47, 26.12 104.47 M134.02 104.47 C93.31 104.47, 52.6 104.47, 26.12 104.47 M26.12 104.47 C8.71 104.47, 0 95.76, 0 78.35 M26.12 104.47 C8.71 104.47, 0 95.76, 0 78.35 M0 78.35 C0 62.11, 0 45.88, 0 26.12 M0 78.35 C0 65.59, 0 52.83, 0 26.12 M0 26.12 C0 8.71, 8.71 0, 26.12 0 M0 26.12 C0 8.71, 8.71 0, 26.12 0" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(587.8631448507463 496.9981461647299) rotate(0 43.259971618652344 37.5)"><text x="43.259971618652344" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Headless</text><text x="43.259971618652344" y="42.62" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Chromium</text><text x="43.259971618652344" y="67.62" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Browser</text></g><g mask="url(#mask-HBsH0H6tXUv6mshxsia-E)" stroke-linecap="round"><g transform="translate(711.2529877920906 415.0453992464066) rotate(0 82.41956624543428 1.2977206098070697)"><path d="M0 0 C27.47 0.43, 137.37 2.16, 164.84 2.6 M0 0 C27.47 0.43, 137.37 2.16, 164.84 2.6" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(711.2529877920906 415.0453992464066) rotate(0 82.41956624543428 1.2977206098070697)"><path d="M141.22 10.78 C147.42 8.63, 153.62 6.48, 164.84 2.6 M141.22 10.78 C148.05 8.41, 154.88 6.04, 164.84 2.6" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(711.2529877920906 415.0453992464066) rotate(0 82.41956624543428 1.2977206098070697)"><path d="M141.48 -6.32 C147.62 -3.98, 153.75 -1.64, 164.84 2.6 M141.48 -6.32 C148.24 -3.74, 155 -1.16, 164.84 2.6" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g></g><mask id="mask-HBsH0H6tXUv6mshxsia-E"><rect x="0" y="0" fill="#fff" width="976.0921202829592" height="517.6408404660208"></rect><rect x="754.4225784158245" y="403.84311985621366" fill="#000" width="78.49995124340057" height="25" opacity="1"></rect></mask><g transform="translate(754.4225784158245 403.84311985621366) rotate(0 39.24997562170029 12.5)"><text x="39.24997562170029" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">json rpc</text></g><g stroke-linecap="round"><g transform="translate(880.5968822997742 533.4353754623069) rotate(0 -84.20194488554768 0)"><path d="M0 0 C-28.07 0, -140.34 0, -168.4 0 M0 0 C-28.07 0, -140.34 0, -168.4 0" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(880.5968822997742 533.4353754623069) rotate(0 -84.20194488554768 0)"><path d="M-144.91 -8.55 C-154.19 -5.17, -163.47 -1.8, -168.4 0 M-144.91 -8.55 C-153.52 -5.42, -162.13 -2.28, -168.4 0" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(880.5968822997742 533.4353754623069) rotate(0 -84.20194488554768 0)"><path d="M-144.91 8.55 C-154.19 5.17, -163.47 1.8, -168.4 0 M-144.91 8.55 C-153.52 5.42, -162.13 2.28, -168.4 0" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g></g><mask></mask><g mask="url(#mask-UgtcBzgCM4CTg2j1I8iOA)" stroke-linecap="round"><g transform="translate(300.44161443964924 417.2055379619113) rotate(0 124.3358106169405 0.295333746717481)"><path d="M0 0 C41.45 0.1, 207.23 0.49, 248.67 0.59 M0 0 C41.45 0.1, 207.23 0.49, 248.67 0.59" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(300.44161443964924 417.2055379619113) rotate(0 124.3358106169405 0.295333746717481)"><path d="M225.16 9.09 C233.43 6.1, 241.7 3.11, 248.67 0.59 M225.16 9.09 C231.21 6.9, 237.25 4.72, 248.67 0.59" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(300.44161443964924 417.2055379619113) rotate(0 124.3358106169405 0.295333746717481)"><path d="M225.2 -8.02 C233.46 -4.99, 241.71 -1.96, 248.67 0.59 M225.2 -8.02 C231.24 -5.8, 237.27 -3.59, 248.67 0.59" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g></g><mask id="mask-UgtcBzgCM4CTg2j1I8iOA"><rect x="0" y="0" fill="#fff" width="649.1132356735302" height="517.7962054553462"></rect><rect x="366.9174662290942" y="405.0008717086288" fill="#000" width="115.71991765499115" height="25" opacity="1"></rect></mask><g transform="translate(366.91746622909415 405.0008717086288) rotate(0 57.859958827495575 12.5)"><text x="57.859958827495575" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">docker exec</text></g><g stroke-linecap="round" transform="translate(117.92271401123094 10) rotate(0 70.56965820659423 70.56965820659423)"><path d="M141.14 70.57 C141.14 73.2, 140.99 75.85, 140.7 78.47 C140.4 81.09, 139.96 83.71, 139.37 86.27 C138.78 88.84, 138.05 91.39, 137.18 93.88 C136.31 96.36, 135.29 98.82, 134.15 101.19 C133.01 103.56, 131.72 105.88, 130.32 108.11 C128.92 110.35, 127.39 112.51, 125.74 114.57 C124.1 116.63, 122.33 118.61, 120.47 120.47 C118.61 122.33, 116.63 124.1, 114.57 125.74 C112.51 127.39, 110.35 128.92, 108.11 130.32 C105.88 131.72, 103.56 133.01, 101.19 134.15 C98.82 135.29, 96.36 136.31, 93.88 137.18 C91.39 138.05, 88.84 138.78, 86.27 139.37 C83.71 139.96, 81.09 140.4, 78.47 140.7 C75.85 140.99, 73.2 141.14, 70.57 141.14 C67.94 141.14, 65.29 140.99, 62.67 140.7 C60.05 140.4, 57.43 139.96, 54.87 139.37 C52.3 138.78, 49.75 138.05, 47.26 137.18 C44.78 136.31, 42.32 135.29, 39.95 134.15 C37.58 133.01, 35.25 131.72, 33.02 130.32 C30.79 128.92, 28.63 127.39, 26.57 125.74 C24.51 124.1, 22.53 122.33, 20.67 120.47 C18.81 118.61, 17.04 116.63, 15.4 114.57 C13.75 112.51, 12.22 110.35, 10.82 108.11 C9.42 105.88, 8.13 103.56, 6.99 101.19 C5.85 98.82, 4.83 96.36, 3.96 93.88 C3.09 91.39, 2.36 88.84, 1.77 86.27 C1.18 83.71, 0.74 81.09, 0.44 78.47 C0.15 75.85, 0 73.2, 0 70.57 C0 67.94, 0.15 65.29, 0.44 62.67 C0.74 60.05, 1.18 57.43, 1.77 54.87 C2.36 52.3, 3.09 49.75, 3.96 47.26 C4.83 44.78, 5.85 42.32, 6.99 39.95 C8.13 37.58, 9.42 35.25, 10.82 33.02 C12.22 30.79, 13.75 28.63, 15.4 26.57 C17.04 24.51, 18.81 22.53, 20.67 20.67 C22.53 18.81, 24.51 17.04, 26.57 15.4 C28.63 13.75, 30.79 12.22, 33.02 10.82 C35.25 9.42, 37.58 8.13, 39.95 6.99 C42.32 5.85, 44.78 4.83, 47.26 3.96 C49.75 3.09, 52.3 2.36, 54.87 1.77 C57.43 1.18, 60.05 0.74, 62.67 0.44 C65.29 0.15, 67.94 0, 70.57 0 C73.2 0, 75.85 0.15, 78.47 0.44 C81.09 0.74, 83.71 1.18, 86.27 1.77 C88.84 2.36, 91.39 3.09, 93.88 3.96 C96.36 4.83, 98.82 5.85, 101.19 6.99 C103.56 8.13, 105.88 9.42, 108.11 10.82 C110.35 12.22, 112.51 13.75, 114.57 15.4 C116.63 17.04, 118.61 18.81, 120.47 20.67 C122.33 22.53, 124.1 24.51, 125.74 26.57 C127.39 28.63, 128.92 30.79, 130.32 33.02 C131.72 35.25, 133.01 37.58, 134.15 39.95 C135.29 42.32, 136.31 44.78, 137.18 47.26 C138.05 49.75, 138.78 52.3, 139.37 54.87 C139.96 57.43, 140.4 60.05, 140.7 62.67 C140.99 65.29, 141.07 69.25, 141.14 70.57 C141.21 71.89, 141.21 69.25, 141.14 70.57" stroke="none" stroke-width="0" fill="#e6fcf5"></path><path d="M141.14 70.57 C141.14 73.2, 140.99 75.85, 140.7 78.47 C140.4 81.09, 139.96 83.71, 139.37 86.27 C138.78 88.84, 138.05 91.39, 137.18 93.88 C136.31 96.36, 135.29 98.82, 134.15 101.19 C133.01 103.56, 131.72 105.88, 130.32 108.11 C128.92 110.35, 127.39 112.51, 125.74 114.57 C124.1 116.63, 122.33 118.61, 120.47 120.47 C118.61 122.33, 116.63 124.1, 114.57 125.74 C112.51 127.39, 110.35 128.92, 108.11 130.32 C105.88 131.72, 103.56 133.01, 101.19 134.15 C98.82 135.29, 96.36 136.31, 93.88 137.18 C91.39 138.05, 88.84 138.78, 86.27 139.37 C83.71 139.96, 81.09 140.4, 78.47 140.7 C75.85 140.99, 73.2 141.14, 70.57 141.14 C67.94 141.14, 65.29 140.99, 62.67 140.7 C60.05 140.4, 57.43 139.96, 54.87 139.37 C52.3 138.78, 49.75 138.05, 47.26 137.18 C44.78 136.31, 42.32 135.29, 39.95 134.15 C37.58 133.01, 35.25 131.72, 33.02 130.32 C30.79 128.92, 28.63 127.39, 26.57 125.74 C24.51 124.1, 22.53 122.33, 20.67 120.47 C18.81 118.61, 17.04 116.63, 15.4 114.57 C13.75 112.51, 12.22 110.35, 10.82 108.11 C9.42 105.88, 8.13 103.56, 6.99 101.19 C5.85 98.82, 4.83 96.36, 3.96 93.88 C3.09 91.39, 2.36 88.84, 1.77 86.27 C1.18 83.71, 0.74 81.09, 0.44 78.47 C0.15 75.85, 0 73.2, 0 70.57 C0 67.94, 0.15 65.29, 0.44 62.67 C0.74 60.05, 1.18 57.43, 1.77 54.87 C2.36 52.3, 3.09 49.75, 3.96 47.26 C4.83 44.78, 5.85 42.32, 6.99 39.95 C8.13 37.58, 9.42 35.25, 10.82 33.02 C12.22 30.79, 13.75 28.63, 15.4 26.57 C17.04 24.51, 18.81 22.53, 20.67 20.67 C22.53 18.81, 24.51 17.04, 26.57 15.4 C28.63 13.75, 30.79 12.22, 33.02 10.82 C35.25 9.42, 37.58 8.13, 39.95 6.99 C42.32 5.85, 44.78 4.83, 47.26 3.96 C49.75 3.09, 52.3 2.36, 54.87 1.77 C57.43 1.18, 60.05 0.74, 62.67 0.44 C65.29 0.15, 67.94 0, 70.57 0 C73.2 0, 75.85 0.15, 78.47 0.44 C81.09 0.74, 83.71 1.18, 86.27 1.77 C88.84 2.36, 91.39 3.09, 93.88 3.96 C96.36 4.83, 98.82 5.85, 101.19 6.99 C103.56 8.13, 105.88 9.42, 108.11 10.82 C110.35 12.22, 112.51 13.75, 114.57 15.4 C116.63 17.04, 118.61 18.81, 120.47 20.67 C122.33 22.53, 124.1 24.51, 125.74 26.57 C127.39 28.63, 128.92 30.79, 130.32 33.02 C131.72 35.25, 133.01 37.58, 134.15 39.95 C135.29 42.32, 136.31 44.78, 137.18 47.26 C138.05 49.75, 138.78 52.3, 139.37 54.87 C139.96 57.43, 140.4 60.05, 140.7 62.67 C140.99 65.29, 141.07 69.25, 141.14 70.57 C141.21 71.89, 141.21 69.25, 141.14 70.57" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(161.26210178165985 55.66937434269454) rotate(0 27.329986572265625 25)"><text x="27.329986572265625" y="17.619999999999997" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Model</text><text x="27.329986572265625" y="42.62" font-family="Excalifont, Xiaolai, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="middle" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">API</text></g></svg>
@@ -1,45 +0,0 @@
1
- """A mock dm_env for unit testing."""
2
-
3
- from typing import Any
4
-
5
- import dm_env
6
- from dm_env import specs
7
-
8
-
9
- class MockEnvironment(dm_env.Environment):
10
- """A Mock DM environment."""
11
-
12
- def __init__(self, context):
13
- """Initializes the environment."""
14
- super().__init__()
15
- self._last_command = ""
16
-
17
- def reset(self) -> dm_env.TimeStep:
18
- """Starts a new sequence and returns the first `TimeStep` of this sequence."""
19
- self._last_command = ""
20
- return dm_env.restart(observation=self.get_observations())
21
-
22
- def step(self, action: list[str]) -> dm_env.TimeStep:
23
- """Updates the environment according to the action and returns a `TimeStep`."""
24
- self._last_command = " ".join(action)
25
- return dm_env.transition(
26
- reward=0.0,
27
- observation=self.get_observations(),
28
- )
29
-
30
- def observation_spec(self) -> dict[str, specs.Array]:
31
- """Defines the observations provided by the environment."""
32
- obs_shapes = {
33
- "last_command": specs.Array(shape=(), dtype=str, name="last_command"),
34
- }
35
- return obs_shapes
36
-
37
- def action_spec(self) -> specs.Array:
38
- """Defines the actions that should be provided to `step`."""
39
- return specs.Array(shape=(), dtype=str, name="command")
40
-
41
- def get_observations(self) -> dict[str, Any]:
42
- """Returns dictionary containing observations."""
43
- return {
44
- "last_command": self._last_command,
45
- }
@@ -1,50 +0,0 @@
1
- import logging
2
- from os import getenv
3
-
4
- from playwright.async_api import Browser, BrowserContext, Playwright, async_playwright
5
-
6
-
7
- class PlaywrightBrowser:
8
- """Stores the browser and creates new contexts."""
9
-
10
- WIDTH = 1280
11
- HEIGHT = 1080
12
- _playwright: Playwright | None = None
13
-
14
- @classmethod
15
- async def create(cls, headless: bool | None = None) -> "PlaywrightBrowser":
16
- if PlaywrightBrowser._playwright is None:
17
- PlaywrightBrowser._playwright = await async_playwright().start()
18
-
19
- headless = True if headless is None else headless
20
- logging.info(
21
- "Starting chromium in %s mode.", "headless" if headless else "headful"
22
- )
23
-
24
- return PlaywrightBrowser(
25
- await PlaywrightBrowser._playwright.chromium.launch(
26
- headless=headless,
27
- # Required for Gmail signup see
28
- # https://stackoverflow.com/questions/65139098/how-to-login-to-google-account-with-playwright
29
- args=["--disable-blink-features=AutomationControlled"],
30
- )
31
- )
32
-
33
- def __init__(self, browser: Browser) -> None:
34
- self._browser = browser
35
-
36
- async def get_new_context(self) -> BrowserContext:
37
- return await self._browser.new_context(
38
- geolocation={"longitude": -0.12, "latitude": 51},
39
- locale="en-GB",
40
- permissions=["geolocation"],
41
- timezone_id="Europe/London",
42
- viewport={"width": self.WIDTH, "height": self.HEIGHT},
43
- ignore_https_errors=getenv("IGNORE_HTTPS_ERRORS", "") != "",
44
- )
45
-
46
- async def close(self) -> None:
47
- await self._browser.close()
48
- if PlaywrightBrowser._playwright is not None:
49
- await PlaywrightBrowser._playwright.stop()
50
- PlaywrightBrowser._playwright = None
@@ -1,48 +0,0 @@
1
- """A crawler implementation using Playwright.
2
-
3
- Largely based on https://github.com/web-arena-x/webarena
4
- """
5
-
6
- from __future__ import annotations
7
-
8
- from asyncio.futures import Future
9
-
10
- from playwright.async_api import BrowserContext, Page
11
-
12
- from playwright_page_crawler import PageCrawler
13
-
14
-
15
- class PlaywrightCrawler:
16
- @classmethod
17
- async def create(
18
- cls, browser_context: BrowserContext, device_scale_factor: float | None = None
19
- ) -> PlaywrightCrawler:
20
- page_crawler = await PageCrawler.create(
21
- await browser_context.new_page(), device_scale_factor
22
- )
23
-
24
- return PlaywrightCrawler(browser_context, page_crawler, device_scale_factor)
25
-
26
- def __init__(
27
- self,
28
- browser_context: BrowserContext,
29
- page_crawler: PageCrawler,
30
- device_scale_factor: float | None,
31
- ):
32
- self._device_scale_factor = device_scale_factor
33
- self._page_future = Future[PageCrawler]()
34
- self._page_future.set_result(page_crawler)
35
- browser_context.on("page", self._on_page)
36
-
37
- @property
38
- async def current_page(self) -> PageCrawler:
39
- return await self._page_future
40
-
41
- async def _on_page(self, new_page: Page):
42
- # we know we're switching pages, but it will take time to get the new page crawler, so
43
- # reset the future to force new callers to wait.
44
- # TODO: A race remains in the case that we get multiple on_pages before the first one sets the result
45
- self._page_future = Future()
46
- self._page_future.set_result(
47
- await PageCrawler.create(new_page, self._device_scale_factor)
48
- )