inspect-ai 0.3.70__py3-none-any.whl → 0.3.71__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 +134 -26
- 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/_resources/tool/_run.py +1 -1
- 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.71.dist-info}/METADATA +1 -1
- {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.71.dist-info}/RECORD +197 -182
- {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.71.dist-info}/WHEEL +1 -1
- inspect_ai/_view/www/node_modules/flatted/python/flatted.py +0 -149
- inspect_ai/_view/www/node_modules/flatted/python/test.py +0 -63
- 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/_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.71.dist-info}/LICENSE +0 -0
- {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.71.dist-info}/entry_points.txt +0 -0
- {inspect_ai-0.3.70.dist-info → inspect_ai-0.3.71.dist-info}/top_level.txt +0 -0
@@ -1,63 +1,213 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
import argparse
|
3
2
|
import sys
|
3
|
+
from typing import Literal
|
4
4
|
|
5
|
-
import
|
6
|
-
from
|
7
|
-
from
|
5
|
+
from constants import DEFAULT_SESSION_NAME, SERVER_PORT
|
6
|
+
from rpc_client_helpers import RPCError, rpc_call
|
7
|
+
from web_browser_rpc_types import (
|
8
|
+
ClickArgs,
|
9
|
+
CrawlerBaseArgs,
|
10
|
+
CrawlerResponse,
|
11
|
+
GoArgs,
|
12
|
+
NewSessionArgs,
|
13
|
+
NewSessionResponse,
|
14
|
+
ScrollArgs,
|
15
|
+
TypeOrSubmitArgs,
|
16
|
+
)
|
8
17
|
|
9
|
-
|
10
|
-
_WORLD_NAME = "WebBrowser"
|
11
|
-
_SESSION_FLAG = "--session_name="
|
18
|
+
_SERVER_URL = f"http://localhost:{SERVER_PORT}/"
|
12
19
|
|
13
20
|
|
14
|
-
def
|
15
|
-
if
|
16
|
-
|
21
|
+
def main() -> None:
|
22
|
+
if len(sys.argv) > 1:
|
23
|
+
command, params = _parse_args()
|
24
|
+
_execute_command(command, params)
|
25
|
+
else:
|
26
|
+
_interactive_mode()
|
17
27
|
|
18
|
-
if cli_args[0].startswith(_SESSION_FLAG):
|
19
|
-
world_name = cli_args[0][len(_SESSION_FLAG) :]
|
20
28
|
|
21
|
-
|
22
|
-
|
29
|
+
def _execute_command(
|
30
|
+
command: str,
|
31
|
+
params: NewSessionArgs
|
32
|
+
| GoArgs
|
33
|
+
| ClickArgs
|
34
|
+
| TypeOrSubmitArgs
|
35
|
+
| ScrollArgs
|
36
|
+
| CrawlerBaseArgs,
|
37
|
+
) -> None:
|
38
|
+
try:
|
39
|
+
if command == "new_session":
|
40
|
+
print(
|
41
|
+
rpc_call(
|
42
|
+
_SERVER_URL, command, dict(params), NewSessionResponse
|
43
|
+
).session_name
|
44
|
+
)
|
23
45
|
else:
|
24
|
-
|
46
|
+
response = rpc_call(
|
47
|
+
_SERVER_URL,
|
48
|
+
command,
|
49
|
+
dict(params),
|
50
|
+
CrawlerResponse,
|
51
|
+
)
|
52
|
+
for key, value in vars(response).items():
|
53
|
+
if value is not None:
|
54
|
+
print(key, ": ", value)
|
25
55
|
|
26
|
-
|
27
|
-
|
56
|
+
except RPCError as rpc_error:
|
57
|
+
_return_error(f"error: {rpc_error}")
|
28
58
|
|
29
59
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
60
|
+
def _interactive_mode() -> None:
|
61
|
+
print(
|
62
|
+
"Welcome to the Playwright Crawler interactive mode!\n"
|
63
|
+
"commands:\n"
|
64
|
+
" web_go <URL> - goes to the specified url.\n"
|
65
|
+
" web_click <ELEMENT_ID> - clicks on a given element.\n"
|
66
|
+
" web_scroll <up/down> - scrolls up or down one page.\n"
|
67
|
+
" web_forward - navigates forward a page.\n"
|
68
|
+
" web_back - navigates back a page.\n"
|
69
|
+
" web_refresh - reloads current page (F5).\n"
|
70
|
+
" web_type <ELEMENT_ID> <TEXT> - types the specified text into the input with the specified id.\n"
|
71
|
+
" web_type_submit <ELEMENT_ID> <TEXT> - types the specified text into the input with the specified id and presses ENTER to submit the form."
|
72
|
+
)
|
73
|
+
|
74
|
+
session_created = False
|
75
|
+
while True:
|
76
|
+
try:
|
77
|
+
user_input = input("Enter command: ").strip()
|
78
|
+
if user_input.lower() in {"exit", "quit"}:
|
79
|
+
break
|
80
|
+
args = user_input.split()
|
81
|
+
sys.argv = ["cli"] + args
|
82
|
+
command, params = _parse_args()
|
83
|
+
print(f"command: {command}, params: {params}")
|
84
|
+
if not session_created:
|
85
|
+
_execute_command("new_session", NewSessionArgs(headful=True))
|
86
|
+
session_created = True
|
87
|
+
_execute_command(command, params)
|
88
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
89
|
+
print(f"Error: {e}")
|
90
|
+
|
91
|
+
|
92
|
+
def _return_error(error: str) -> None:
|
93
|
+
print(error, file=sys.stderr)
|
94
|
+
sys.exit(1)
|
95
|
+
|
96
|
+
|
97
|
+
def _create_main_parser() -> argparse.ArgumentParser:
|
98
|
+
parser = argparse.ArgumentParser(prog="web_client")
|
99
|
+
parser.add_argument(
|
100
|
+
"--session_name",
|
101
|
+
type=str,
|
102
|
+
required=False,
|
103
|
+
default=DEFAULT_SESSION_NAME,
|
104
|
+
help="Session name",
|
47
105
|
)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
)
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
106
|
+
return parser
|
107
|
+
|
108
|
+
|
109
|
+
def _create_command_parser() -> argparse.ArgumentParser:
|
110
|
+
result = argparse.ArgumentParser(prog="web_client")
|
111
|
+
|
112
|
+
subparsers = result.add_subparsers(dest="command", required=True)
|
113
|
+
|
114
|
+
go_parser = subparsers.add_parser("web_go")
|
115
|
+
go_parser.add_argument("url", type=str, help="URL to navigate to")
|
116
|
+
|
117
|
+
click_parser = subparsers.add_parser("web_click")
|
118
|
+
click_parser.add_argument("element_id", type=str, help="ID of the element to click")
|
119
|
+
|
120
|
+
scroll_parser = subparsers.add_parser("web_scroll")
|
121
|
+
scroll_parser.add_argument(
|
122
|
+
"direction",
|
123
|
+
type=str,
|
124
|
+
choices=["up", "down"],
|
125
|
+
help="Direction to scroll (up or down)",
|
126
|
+
)
|
127
|
+
subparsers.add_parser("web_forward")
|
128
|
+
subparsers.add_parser("web_back")
|
129
|
+
subparsers.add_parser("web_refresh")
|
130
|
+
|
131
|
+
type_parser = subparsers.add_parser("web_type")
|
132
|
+
type_parser.add_argument(
|
133
|
+
"element_id", type=str, help="ID of the element to type into"
|
134
|
+
)
|
135
|
+
type_parser.add_argument("text", type=str, help="The text to type")
|
136
|
+
|
137
|
+
submit_parser = subparsers.add_parser("web_type_submit")
|
138
|
+
submit_parser.add_argument(
|
139
|
+
"element_id",
|
140
|
+
type=str,
|
141
|
+
help="ID of the element to type into and submit",
|
142
|
+
)
|
143
|
+
submit_parser.add_argument("text", type=str, help="The text to type")
|
144
|
+
|
145
|
+
# Add common argument to all subparsers
|
146
|
+
for name, subparser in subparsers.choices.items():
|
147
|
+
if name != "new_session":
|
148
|
+
subparser.add_argument(
|
149
|
+
"--session_name",
|
150
|
+
type=str,
|
151
|
+
nargs="?",
|
152
|
+
required=False,
|
153
|
+
default=DEFAULT_SESSION_NAME,
|
154
|
+
help="Session name",
|
155
|
+
)
|
156
|
+
|
157
|
+
return result
|
158
|
+
|
159
|
+
|
160
|
+
main_parser = _create_main_parser()
|
161
|
+
command_parser = _create_command_parser()
|
162
|
+
|
163
|
+
|
164
|
+
def _parse_args() -> (
|
165
|
+
tuple[Literal["web_go"], GoArgs]
|
166
|
+
| tuple[Literal["web_click"], ClickArgs]
|
167
|
+
| tuple[Literal["web_type", "web_type_submit"], TypeOrSubmitArgs]
|
168
|
+
| tuple[Literal["web_scroll"], ScrollArgs]
|
169
|
+
| tuple[Literal["web_forward", "web_back", "web_refresh"], CrawlerBaseArgs]
|
170
|
+
):
|
171
|
+
# web_client.py supports a very non-standard command line. It has a required named
|
172
|
+
# parameter, --session_name, before the command.
|
173
|
+
# Unfortunately, because we can't break backwards compatibility, we're stuck
|
174
|
+
# with that. To properly parse it, we'll be forced to have a separate parser
|
175
|
+
# for --session_name and merge the results with the normal command parser.
|
176
|
+
|
177
|
+
main_args, remaining_args = main_parser.parse_known_args()
|
178
|
+
session_name = main_args.session_name or DEFAULT_SESSION_NAME
|
179
|
+
|
180
|
+
command_args = command_parser.parse_args(remaining_args)
|
181
|
+
command_args_dict = vars(command_args)
|
182
|
+
|
183
|
+
match command_args.command:
|
184
|
+
case "web_go":
|
185
|
+
return command_args_dict["command"], GoArgs(
|
186
|
+
url=command_args_dict["url"],
|
187
|
+
session_name=session_name,
|
188
|
+
)
|
189
|
+
case "web_click":
|
190
|
+
return command_args_dict["command"], ClickArgs(
|
191
|
+
element_id=command_args_dict["element_id"],
|
192
|
+
session_name=session_name,
|
193
|
+
)
|
194
|
+
case "web_type" | "web_type_submit":
|
195
|
+
return command_args_dict["command"], TypeOrSubmitArgs(
|
196
|
+
element_id=command_args_dict["element_id"],
|
197
|
+
text=command_args_dict["text"],
|
198
|
+
session_name=session_name,
|
199
|
+
)
|
200
|
+
case "web_scroll":
|
201
|
+
return command_args_dict["command"], ScrollArgs(
|
202
|
+
direction=command_args_dict["direction"],
|
203
|
+
session_name=session_name,
|
204
|
+
)
|
205
|
+
case "web_forward" | "web_back" | "web_refresh":
|
206
|
+
return command_args_dict["command"], CrawlerBaseArgs(
|
207
|
+
session_name=session_name,
|
208
|
+
)
|
209
|
+
case _:
|
210
|
+
raise ValueError("Unexpected command")
|
61
211
|
|
62
212
|
|
63
213
|
if __name__ == "__main__":
|
@@ -1,33 +1,34 @@
|
|
1
|
-
|
1
|
+
import argparse
|
2
|
+
import sys
|
2
3
|
|
3
|
-
import
|
4
|
-
from
|
5
|
-
from
|
4
|
+
from constants import SERVER_PORT
|
5
|
+
from rpc_client_helpers import RPCError, rpc_call
|
6
|
+
from web_browser_rpc_types import NewSessionArgs, NewSessionResponse
|
6
7
|
|
7
|
-
_DM_ENV_BASE_PORT = 9443
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
options = [
|
14
|
-
("grpc.keepalive_permit_without_calls", 0),
|
15
|
-
("grpc.keepalive_timeout_ms", 20000),
|
16
|
-
("grpc.http2.max_pings_without_data", 0),
|
17
|
-
("grpc.http2.max_ping_strikes", 0),
|
18
|
-
("grpc.http2.min_recv_ping_interval_without_data_ms", 0),
|
19
|
-
]
|
20
|
-
# Creating a world with the headless browser.
|
21
|
-
channel = grpc.secure_channel(
|
22
|
-
f"localhost:{_DM_ENV_BASE_PORT}",
|
23
|
-
grpc.local_channel_credentials(),
|
24
|
-
options=options,
|
9
|
+
def main() -> None:
|
10
|
+
parser = argparse.ArgumentParser(prog="web_client_new_session")
|
11
|
+
parser.add_argument(
|
12
|
+
"--headful", action="store_true", help="Run in headful mode for testing"
|
25
13
|
)
|
26
|
-
|
27
|
-
|
28
|
-
|
14
|
+
args_class = parser.parse_args()
|
15
|
+
args_dict = vars(args_class)
|
16
|
+
# TODO: Frick. this does no validation
|
17
|
+
params_typed_dict = NewSessionArgs(headful=args_dict["headful"])
|
18
|
+
params = dict(params_typed_dict)
|
29
19
|
|
30
|
-
|
20
|
+
try:
|
21
|
+
print(
|
22
|
+
rpc_call(
|
23
|
+
f"http://localhost:{SERVER_PORT}/",
|
24
|
+
"new_session",
|
25
|
+
params,
|
26
|
+
NewSessionResponse,
|
27
|
+
).session_name
|
28
|
+
)
|
29
|
+
except RPCError as rpc_error:
|
30
|
+
print(rpc_error, file=sys.stderr)
|
31
|
+
sys.exit(1)
|
31
32
|
|
32
33
|
|
33
34
|
if __name__ == "__main__":
|
@@ -1,52 +1,191 @@
|
|
1
|
-
|
1
|
+
import threading
|
2
|
+
from typing import Awaitable, Callable, Unpack
|
2
3
|
|
3
|
-
from
|
4
|
+
from aiohttp.web import Application, Request, Response, run_app
|
5
|
+
from jsonrpcserver import Result, Success, async_dispatch, method
|
4
6
|
|
5
|
-
import
|
6
|
-
import
|
7
|
-
import
|
8
|
-
from
|
9
|
-
from
|
7
|
+
from constants import DEFAULT_SESSION_NAME, SERVER_PORT
|
8
|
+
from playwright_browser import PlaywrightBrowser
|
9
|
+
from playwright_crawler import PlaywrightCrawler
|
10
|
+
from scale_factor import get_screen_scale_factor
|
11
|
+
from web_browser_rpc_types import (
|
12
|
+
ClickArgs,
|
13
|
+
CrawlerBaseArgs,
|
14
|
+
CrawlerResponse,
|
15
|
+
GoArgs,
|
16
|
+
NewSessionArgs,
|
17
|
+
NewSessionResponse,
|
18
|
+
ScrollArgs,
|
19
|
+
TypeOrSubmitArgs,
|
20
|
+
)
|
10
21
|
|
11
|
-
_DM_ENV_BASE_PORT = 9443
|
12
22
|
|
23
|
+
class Sessions:
|
24
|
+
def __init__(self) -> None:
|
25
|
+
self._lock = threading.Lock()
|
26
|
+
self._browser: PlaywrightBrowser | None = None
|
27
|
+
self._sessions: dict[str, PlaywrightCrawler] = {}
|
13
28
|
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
env_service = dm_env_servicer.EnvironmentService(web_environment.WebEnvironment)
|
31
|
-
dm_env_rpc_pb2_grpc.add_EnvironmentServicer_to_server(env_service, grpc_server)
|
29
|
+
async def new_session(self, headful: bool) -> str:
|
30
|
+
with self._lock:
|
31
|
+
if not self._browser:
|
32
|
+
self._browser = await PlaywrightBrowser.create(headless=not headful)
|
33
|
+
current_count = len(self._sessions)
|
34
|
+
name = (
|
35
|
+
DEFAULT_SESSION_NAME
|
36
|
+
if current_count == 0
|
37
|
+
else f"{DEFAULT_SESSION_NAME}_{current_count}"
|
38
|
+
)
|
39
|
+
crawler = await PlaywrightCrawler.create(
|
40
|
+
await self._browser.get_new_context(),
|
41
|
+
device_scale_factor=get_screen_scale_factor() if headful else 1,
|
42
|
+
)
|
43
|
+
self._sessions[name] = crawler
|
44
|
+
return name
|
32
45
|
|
33
|
-
|
34
|
-
|
35
|
-
|
46
|
+
async def get_crawler_for_session(self, name: str) -> PlaywrightCrawler:
|
47
|
+
if not self._sessions:
|
48
|
+
await self.new_session(False)
|
49
|
+
return self._sessions[name]
|
36
50
|
|
37
|
-
grpc_server.start()
|
38
51
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
52
|
+
sessions = Sessions()
|
53
|
+
|
54
|
+
|
55
|
+
@method
|
56
|
+
async def new_session(**kwargs: Unpack[NewSessionArgs]) -> NewSessionResponse:
|
57
|
+
return Success(
|
58
|
+
NewSessionResponse(
|
59
|
+
session_name=await sessions.new_session(kwargs.get("headful", False))
|
60
|
+
).model_dump()
|
44
61
|
)
|
45
|
-
connection = dm_env_connection.Connection(channel)
|
46
|
-
connection.send(dm_env_rpc_pb2.CreateWorldRequest())
|
47
|
-
connection.close()
|
48
62
|
|
49
|
-
|
63
|
+
|
64
|
+
@method
|
65
|
+
async def web_go(**kwargs: Unpack[GoArgs]) -> Result:
|
66
|
+
async def handler(crawler: PlaywrightCrawler):
|
67
|
+
await (await crawler.current_page).go_to_url(kwargs["url"])
|
68
|
+
|
69
|
+
return await _execute_crawler_command(kwargs["session_name"], handler)
|
70
|
+
|
71
|
+
|
72
|
+
@method
|
73
|
+
async def web_click(**kwargs: Unpack[ClickArgs]) -> Result:
|
74
|
+
async def handler(crawler: PlaywrightCrawler):
|
75
|
+
await (await crawler.current_page).click(kwargs["element_id"])
|
76
|
+
|
77
|
+
return await _execute_crawler_command(kwargs["session_name"], handler)
|
78
|
+
|
79
|
+
|
80
|
+
@method
|
81
|
+
async def web_scroll(**kwargs: Unpack[ScrollArgs]) -> Result:
|
82
|
+
async def handler(crawler: PlaywrightCrawler):
|
83
|
+
await (await crawler.current_page).scroll(kwargs["direction"])
|
84
|
+
|
85
|
+
return await _execute_crawler_command(kwargs["session_name"], handler)
|
86
|
+
|
87
|
+
|
88
|
+
@method
|
89
|
+
async def web_forward(**kwargs: Unpack[CrawlerBaseArgs]) -> Result:
|
90
|
+
async def handler(crawler: PlaywrightCrawler):
|
91
|
+
await (await crawler.current_page).forward()
|
92
|
+
|
93
|
+
return await _execute_crawler_command(kwargs["session_name"], handler)
|
94
|
+
|
95
|
+
|
96
|
+
@method
|
97
|
+
async def web_back(**kwargs: Unpack[CrawlerBaseArgs]) -> Result:
|
98
|
+
async def handler(crawler: PlaywrightCrawler):
|
99
|
+
await (await crawler.current_page).back()
|
100
|
+
|
101
|
+
return await _execute_crawler_command(kwargs["session_name"], handler)
|
102
|
+
|
103
|
+
|
104
|
+
@method
|
105
|
+
async def web_refresh(**kwargs: Unpack[CrawlerBaseArgs]) -> Result:
|
106
|
+
async def handler(crawler: PlaywrightCrawler):
|
107
|
+
await (await crawler.current_page).refresh()
|
108
|
+
|
109
|
+
return await _execute_crawler_command(kwargs["session_name"], handler)
|
110
|
+
|
111
|
+
|
112
|
+
@method
|
113
|
+
async def web_type(**kwargs: Unpack[TypeOrSubmitArgs]) -> Result:
|
114
|
+
async def handler(crawler: PlaywrightCrawler):
|
115
|
+
await (await crawler.current_page).type(
|
116
|
+
kwargs["element_id"], _str_from_str_or_list(kwargs["text"])
|
117
|
+
)
|
118
|
+
|
119
|
+
return await _execute_crawler_command(kwargs["session_name"], handler)
|
120
|
+
|
121
|
+
|
122
|
+
@method
|
123
|
+
async def web_type_submit(**kwargs: Unpack[TypeOrSubmitArgs]) -> Result:
|
124
|
+
async def handler(crawler: PlaywrightCrawler):
|
125
|
+
await (await crawler.current_page).clear(kwargs["element_id"])
|
126
|
+
await (await crawler.current_page).type(
|
127
|
+
kwargs["element_id"], _str_from_str_or_list(kwargs["text"]) + "\n"
|
128
|
+
)
|
129
|
+
|
130
|
+
return await _execute_crawler_command(kwargs["session_name"], handler)
|
131
|
+
|
132
|
+
|
133
|
+
async def _execute_crawler_command(
|
134
|
+
session_name: str, handler: Callable[[PlaywrightCrawler], Awaitable[None]]
|
135
|
+
) -> Result:
|
136
|
+
if not sessions:
|
137
|
+
await new_session()
|
138
|
+
try:
|
139
|
+
crawler = await sessions.get_crawler_for_session(session_name)
|
140
|
+
await handler(crawler)
|
141
|
+
await (await crawler.current_page).update()
|
142
|
+
|
143
|
+
# If there's a cookies message click to sort it out.
|
144
|
+
await _auto_click_cookies(crawler)
|
145
|
+
|
146
|
+
return Success(
|
147
|
+
CrawlerResponse(
|
148
|
+
web_url=(await crawler.current_page).url.split("?")[0],
|
149
|
+
main_content=(await crawler.current_page).render_main_content(),
|
150
|
+
web_at=(await crawler.current_page).render_at(),
|
151
|
+
error=None,
|
152
|
+
).model_dump()
|
153
|
+
)
|
154
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
155
|
+
return Success(
|
156
|
+
CrawlerResponse(
|
157
|
+
web_url=(await crawler.current_page).url.split("?")[0],
|
158
|
+
web_at="encountered error",
|
159
|
+
error=str(e),
|
160
|
+
).model_dump()
|
161
|
+
)
|
162
|
+
|
163
|
+
|
164
|
+
def _str_from_str_or_list(str_or_list: str | list[str]) -> str:
|
165
|
+
return str_or_list if isinstance(str_or_list, str) else " ".join(str_or_list)
|
166
|
+
|
167
|
+
|
168
|
+
async def _auto_click_cookies(crawler: PlaywrightCrawler):
|
169
|
+
"""Autoclick any cookies popup."""
|
170
|
+
try:
|
171
|
+
accept_node = (await crawler.current_page).lookup_node("<Accept all>")
|
172
|
+
except LookupError:
|
173
|
+
return
|
174
|
+
await (await crawler.current_page).click(accept_node.node_id)
|
175
|
+
await (await crawler.current_page).update()
|
176
|
+
|
177
|
+
|
178
|
+
def main():
|
179
|
+
async def handle_request(request: Request) -> Response:
|
180
|
+
return Response(
|
181
|
+
text=await async_dispatch(await request.text()),
|
182
|
+
content_type="application/json",
|
183
|
+
)
|
184
|
+
|
185
|
+
app = Application()
|
186
|
+
app.router.add_post("/", handle_request)
|
187
|
+
|
188
|
+
run_app(app, port=SERVER_PORT)
|
50
189
|
|
51
190
|
|
52
191
|
if __name__ == "__main__":
|