@elizaos/sweagent-root 2.0.0-alpha
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.
- package/LICENSE +21 -0
- package/README.md +270 -0
- package/package.json +71 -0
- package/python/LICENSE +21 -0
- package/python/config/README.md +15 -0
- package/python/config/bash_only.yaml +222 -0
- package/python/config/benchmarks/250212_sweagent_heavy_sbl.yaml +188 -0
- package/python/config/benchmarks/250225_anthropic_filemap_simple_review.yaml +75 -0
- package/python/config/benchmarks/250522_anthropic_filemap_simple_review.yaml +92 -0
- package/python/config/benchmarks/250526_anthropic_filemap_simple_review_sbl.yaml +93 -0
- package/python/config/benchmarks/anthropic_filemap_multilingual.yaml +66 -0
- package/python/config/coding_challenge.yaml +104 -0
- package/python/config/default.yaml +69 -0
- package/python/config/default_backticks.yaml +69 -0
- package/python/config/default_mm_no_images.yaml +82 -0
- package/python/config/default_mm_with_images.yaml +83 -0
- package/python/config/demo/default.yaml +80 -0
- package/python/config/demo/no_instructions.yaml +69 -0
- package/python/config/demo/only_bash.yaml +60 -0
- package/python/config/exotic/default_shell.yaml +52 -0
- package/python/config/exotic/windowed_replace.yaml +125 -0
- package/python/config/exotic/windowed_replace_late_repro.yaml +127 -0
- package/python/config/human/human.yaml +24 -0
- package/python/config/human/human_demo.yaml +52 -0
- package/python/config/sweagent_0_7/07.yaml +101 -0
- package/python/config/sweagent_0_7/07_fcalling.yaml +100 -0
- package/python/config/sweagent_0_7/07_from_url.yaml +114 -0
- package/python/config/sweagent_0_7/07_thought_action.yaml +102 -0
- package/python/config/sweagent_0_7/07_thought_action_xml.yaml +96 -0
- package/python/mlc_config.json +44 -0
- package/python/pyproject.toml +262 -0
- package/python/sweagent/__init__.py +114 -0
- package/python/sweagent/__main__.py +4 -0
- package/python/sweagent/agent/__init__.py +0 -0
- package/python/sweagent/agent/action_sampler.py +317 -0
- package/python/sweagent/agent/agents.py +1294 -0
- package/python/sweagent/agent/extra/shell_agent.py +106 -0
- package/python/sweagent/agent/history_processors.py +399 -0
- package/python/sweagent/agent/hooks/__init__.py +0 -0
- package/python/sweagent/agent/hooks/abstract.py +139 -0
- package/python/sweagent/agent/hooks/status.py +34 -0
- package/python/sweagent/agent/models.py +896 -0
- package/python/sweagent/agent/problem_statement.py +312 -0
- package/python/sweagent/agent/reviewer.py +664 -0
- package/python/sweagent/environment/__init__.py +0 -0
- package/python/sweagent/environment/hooks/__init__.py +0 -0
- package/python/sweagent/environment/hooks/abstract.py +60 -0
- package/python/sweagent/environment/hooks/status.py +28 -0
- package/python/sweagent/environment/repo.py +219 -0
- package/python/sweagent/environment/swe_env.py +276 -0
- package/python/sweagent/exceptions.py +54 -0
- package/python/sweagent/inspector/README.md +6 -0
- package/python/sweagent/inspector/__init__.py +0 -0
- package/python/sweagent/inspector/favicon.ico +0 -0
- package/python/sweagent/inspector/fileViewer.js +354 -0
- package/python/sweagent/inspector/icons/computer.png +0 -0
- package/python/sweagent/inspector/icons/edit_icon.svg +11 -0
- package/python/sweagent/inspector/icons/swe-agent-logo-50.png +0 -0
- package/python/sweagent/inspector/icons/swellama_blue.png +0 -0
- package/python/sweagent/inspector/icons/swellama_brown.png +0 -0
- package/python/sweagent/inspector/icons/swellama_grey.png +0 -0
- package/python/sweagent/inspector/icons/swellama_tan.png +0 -0
- package/python/sweagent/inspector/index.html +25 -0
- package/python/sweagent/inspector/server.py +354 -0
- package/python/sweagent/inspector/static.py +169 -0
- package/python/sweagent/inspector/style.css +454 -0
- package/python/sweagent/run/__init__.py +0 -0
- package/python/sweagent/run/_progress.py +158 -0
- package/python/sweagent/run/batch_instances.py +419 -0
- package/python/sweagent/run/common.py +387 -0
- package/python/sweagent/run/compare_runs.py +123 -0
- package/python/sweagent/run/extract_pred.py +19 -0
- package/python/sweagent/run/hooks/__init__.py +0 -0
- package/python/sweagent/run/hooks/abstract.py +67 -0
- package/python/sweagent/run/hooks/apply_patch.py +106 -0
- package/python/sweagent/run/hooks/open_pr.py +244 -0
- package/python/sweagent/run/hooks/swe_bench_evaluate.py +113 -0
- package/python/sweagent/run/inspector_cli.py +493 -0
- package/python/sweagent/run/merge_predictions.py +64 -0
- package/python/sweagent/run/quick_stats.py +96 -0
- package/python/sweagent/run/remove_unfinished.py +63 -0
- package/python/sweagent/run/rich_test.py +91 -0
- package/python/sweagent/run/run.py +147 -0
- package/python/sweagent/run/run_batch.py +442 -0
- package/python/sweagent/run/run_replay.py +219 -0
- package/python/sweagent/run/run_shell.py +155 -0
- package/python/sweagent/run/run_single.py +225 -0
- package/python/sweagent/run/run_traj_to_demo.py +85 -0
- package/python/sweagent/tools/__init__.py +0 -0
- package/python/sweagent/tools/bundle.py +57 -0
- package/python/sweagent/tools/commands.py +220 -0
- package/python/sweagent/tools/parsing.py +619 -0
- package/python/sweagent/tools/tools.py +430 -0
- package/python/sweagent/tools/utils.py +108 -0
- package/python/sweagent/types.py +102 -0
- package/python/sweagent/utils/__init__.py +0 -0
- package/python/sweagent/utils/config.py +80 -0
- package/python/sweagent/utils/files.py +27 -0
- package/python/sweagent/utils/github.py +118 -0
- package/python/sweagent/utils/jinja_warnings.py +14 -0
- package/python/sweagent/utils/log.py +175 -0
- package/python/sweagent/utils/patch_formatter.py +152 -0
- package/python/sweagent/utils/serialization.py +45 -0
- package/python/tests/__init__.py +0 -0
- package/python/tests/conftest.py +191 -0
- package/python/tests/test_agent.py +258 -0
- package/python/tests/test_batch_instance.py +43 -0
- package/python/tests/test_commands/_interactive_dummy.py +35 -0
- package/python/tests/test_commands/interactive_dummy_wrapper.sh +29 -0
- package/python/tests/test_data/config_files/dummy_interactive.yaml +62 -0
- package/python/tests/test_data/data_sources/ctf/crypto/Katy/Dockerfile +20 -0
- package/python/tests/test_data/data_sources/ctf/crypto/Katy/README.md +13 -0
- package/python/tests/test_data/data_sources/ctf/crypto/Katy/challenge.json +12 -0
- package/python/tests/test_data/data_sources/ctf/crypto/Katy/customrandom.c +50 -0
- package/python/tests/test_data/data_sources/ctf/crypto/Katy/docker-compose.yml +14 -0
- package/python/tests/test_data/data_sources/ctf/crypto/Katy/release +0 -0
- package/python/tests/test_data/data_sources/ctf/crypto/Katy/server +0 -0
- package/python/tests/test_data/data_sources/ctf/crypto/Katy/solver.py +12 -0
- package/python/tests/test_data/data_sources/ctf/forensics/flash/README.md +16 -0
- package/python/tests/test_data/data_sources/ctf/forensics/flash/challenge.json +9 -0
- package/python/tests/test_data/data_sources/ctf/forensics/flash/flash_c8429a430278283c0e571baebca3d139.zip +0 -0
- package/python/tests/test_data/data_sources/ctf/misc/networking_1/README.md +15 -0
- package/python/tests/test_data/data_sources/ctf/misc/networking_1/challenge.json +10 -0
- package/python/tests/test_data/data_sources/ctf/misc/networking_1/networking.pcap +0 -0
- package/python/tests/test_data/data_sources/ctf/pwn/warmup/Dockerfile +28 -0
- package/python/tests/test_data/data_sources/ctf/pwn/warmup/README.md +14 -0
- package/python/tests/test_data/data_sources/ctf/pwn/warmup/challenge.json +14 -0
- package/python/tests/test_data/data_sources/ctf/pwn/warmup/docker-compose.yml +14 -0
- package/python/tests/test_data/data_sources/ctf/pwn/warmup/flag.txt +1 -0
- package/python/tests/test_data/data_sources/ctf/pwn/warmup/warmup +0 -0
- package/python/tests/test_data/data_sources/ctf/pwn/warmup/warmup.c +26 -0
- package/python/tests/test_data/data_sources/ctf/pwn/warmup/warmup.py +9 -0
- package/python/tests/test_data/data_sources/ctf/rev/rock/README.md +14 -0
- package/python/tests/test_data/data_sources/ctf/rev/rock/challenge.json +8 -0
- package/python/tests/test_data/data_sources/ctf/rev/rock/rock +0 -0
- package/python/tests/test_data/data_sources/ctf/rev/rock/rock.cpp +167 -0
- package/python/tests/test_data/data_sources/ctf/rev/rock/solution.cpp +24 -0
- package/python/tests/test_data/data_sources/ctf/rev/rock/test_solver/solution.py +6 -0
- package/python/tests/test_data/data_sources/ctf/rev/rock/test_solver/test.sh +10 -0
- package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/000-default.conf +18 -0
- package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/Dockerfile +20 -0
- package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/cgi/file.pl +38 -0
- package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/cgi/forms.pl +40 -0
- package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/cgi/hello.pl +11 -0
- package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/challenge.json +12 -0
- package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/docker-compose.yml +14 -0
- package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/flag +1 -0
- package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/index.html +11 -0
- package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/solution.txt +1 -0
- package/python/tests/test_data/data_sources/debug_20240322.json +1 -0
- package/python/tests/test_data/data_sources/expert_instances.yaml +16 -0
- package/python/tests/test_data/data_sources/human_eval.json +1 -0
- package/python/tests/test_data/data_sources/simple_instances.yaml +3 -0
- package/python/tests/test_data/data_sources/simple_instances_long.yaml +30 -0
- package/python/tests/test_data/data_sources/swe-bench-dev-easy.json +1 -0
- package/python/tests/test_data/data_sources/swe-bench-dev-easy_first_only.json +1 -0
- package/python/tests/test_data/data_sources/swe-bench-lite-test.json +1 -0
- package/python/tests/test_data/trajectories/gpt4__swe-agent-test-repo__default_from_url__t-0.00__p-0.95__c-3.00__install-1/6e44b9__sweagenttestrepo-1c2844.traj +342 -0
- package/python/tests/test_data/trajectories/gpt4__swe-agent-test-repo__default_from_url__t-0.00__p-0.95__c-3.00__install-1/solution_missing_colon.py +15 -0
- package/python/tests/test_data/trajectories/gpt4__swe-agent__test-repo__default_from_url__t-0.00__p-0.95__c-3.00__install-1/args.yaml +518 -0
- package/python/tests/test_data/trajectories/gpt4__swe-agent__test-repo__default_from_url__t-0.00__p-0.95__c-3.00__install-1/swe-agent__test-repo-i1.traj +124 -0
- package/python/tests/test_data/trajectories/gpt4__swe-bench-dev-easy_first_only__default__t-0.00__p-0.95__c-3.00__install-1/all_preds.jsonl +1 -0
- package/python/tests/test_data/trajectories/gpt4__swe-bench-dev-easy_first_only__default__t-0.00__p-0.95__c-3.00__install-1/args.yaml +520 -0
- package/python/tests/test_data/trajectories/gpt4__swe-bench-dev-easy_first_only__default__t-0.00__p-0.95__c-3.00__install-1/patches/pydicom__pydicom-1458.patch +18 -0
- package/python/tests/test_data/trajectories/gpt4__swe-bench-dev-easy_first_only__default__t-0.00__p-0.95__c-3.00__install-1/pydicom__pydicom-1458.traj +257 -0
- package/python/tests/test_env.py +66 -0
- package/python/tests/test_env_utils.py +129 -0
- package/python/tests/test_history_processors.py +40 -0
- package/python/tests/test_models.py +23 -0
- package/python/tests/test_openai_live.py +164 -0
- package/python/tests/test_packaging.py +7 -0
- package/python/tests/test_parsing.py +131 -0
- package/python/tests/test_problem_statement_multimodal.py +111 -0
- package/python/tests/test_quick_stats.py +42 -0
- package/python/tests/test_run.py +37 -0
- package/python/tests/test_run_batch.py +110 -0
- package/python/tests/test_run_hooks.py +114 -0
- package/python/tests/test_run_replay.py +33 -0
- package/python/tests/test_run_single.py +125 -0
- package/python/tests/test_tools_command_parsing.py +193 -0
- package/python/tests/test_utils.py +15 -0
- package/python/tests/tools/__init__.py +0 -0
- package/python/tests/tools/conftest.py +12 -0
- package/python/tests/tools/test_default_utils.py +153 -0
- package/python/tests/tools/test_edit_replace.py +0 -0
- package/python/tests/tools/test_split_string.py +82 -0
- package/python/tests/utils.py +29 -0
- package/python/tools/diff_state/bin/_state_diff_state +52 -0
- package/python/tools/diff_state/config.yaml +2 -0
- package/python/tools/edit_anthropic/bin/_state_anthropic +21 -0
- package/python/tools/edit_anthropic/bin/str_replace_editor +710 -0
- package/python/tools/edit_anthropic/config.yaml +56 -0
- package/python/tools/edit_anthropic/install.sh +3 -0
- package/python/tools/filemap/bin/filemap +45 -0
- package/python/tools/filemap/config.yaml +9 -0
- package/python/tools/filemap/install.sh +2 -0
- package/python/tools/forfeit/bin/exit_forfeit +5 -0
- package/python/tools/forfeit/config.yaml +5 -0
- package/python/tools/image_tools/bin/view_image +36 -0
- package/python/tools/image_tools/config.yaml +9 -0
- package/python/tools/multilingual_setup/bin/do_nothing +2 -0
- package/python/tools/multilingual_setup/config.yaml +1 -0
- package/python/tools/multilingual_setup/install.sh +45 -0
- package/python/tools/registry/bin/_read_env +10 -0
- package/python/tools/registry/bin/_write_env +10 -0
- package/python/tools/registry/config.yaml +1 -0
- package/python/tools/registry/install.sh +6 -0
- package/python/tools/registry/lib/__init__.py +0 -0
- package/python/tools/registry/lib/registry.py +56 -0
- package/python/tools/review_on_submit_m/README.md +6 -0
- package/python/tools/review_on_submit_m/bin/submit +54 -0
- package/python/tools/review_on_submit_m/config.yaml +6 -0
- package/python/tools/review_on_submit_m/install.sh +0 -0
- package/python/tools/search/bin/find_file +31 -0
- package/python/tools/search/bin/search_dir +39 -0
- package/python/tools/search/bin/search_file +55 -0
- package/python/tools/search/config.yaml +37 -0
- package/python/tools/search/install.sh +3 -0
- package/python/tools/submit/bin/submit +17 -0
- package/python/tools/submit/config.yaml +5 -0
- package/python/tools/web_browser/bin/click_mouse +41 -0
- package/python/tools/web_browser/bin/close_site +28 -0
- package/python/tools/web_browser/bin/double_click_mouse +37 -0
- package/python/tools/web_browser/bin/drag_mouse +46 -0
- package/python/tools/web_browser/bin/execute_script_on_page +39 -0
- package/python/tools/web_browser/bin/get_console_output +48 -0
- package/python/tools/web_browser/bin/move_mouse +35 -0
- package/python/tools/web_browser/bin/navigate_back +33 -0
- package/python/tools/web_browser/bin/navigate_forward +33 -0
- package/python/tools/web_browser/bin/open_site +36 -0
- package/python/tools/web_browser/bin/press_keys_on_page +51 -0
- package/python/tools/web_browser/bin/reload_page +33 -0
- package/python/tools/web_browser/bin/run_web_browser_server +394 -0
- package/python/tools/web_browser/bin/screenshot_site +38 -0
- package/python/tools/web_browser/bin/scroll_on_page +40 -0
- package/python/tools/web_browser/bin/set_browser_window_size +40 -0
- package/python/tools/web_browser/bin/type_text +34 -0
- package/python/tools/web_browser/bin/wait_time +39 -0
- package/python/tools/web_browser/config.yaml +155 -0
- package/python/tools/web_browser/install.sh +22 -0
- package/python/tools/web_browser/lib/browser_manager.py +404 -0
- package/python/tools/web_browser/lib/web_browser_config.py +33 -0
- package/python/tools/web_browser/lib/web_browser_utils.py +126 -0
- package/python/tools/web_browser/test_console.html +1 -0
- package/python/tools/windowed/bin/_state +25 -0
- package/python/tools/windowed/bin/create +29 -0
- package/python/tools/windowed/bin/goto +37 -0
- package/python/tools/windowed/bin/open +49 -0
- package/python/tools/windowed/bin/scroll_down +12 -0
- package/python/tools/windowed/bin/scroll_up +13 -0
- package/python/tools/windowed/config.yaml +38 -0
- package/python/tools/windowed/install.sh +15 -0
- package/python/tools/windowed/lib/__init__.py +0 -0
- package/python/tools/windowed/lib/flake8_utils.py +147 -0
- package/python/tools/windowed/lib/windowed_file.py +312 -0
- package/python/tools/windowed_edit_linting/bin/edit +128 -0
- package/python/tools/windowed_edit_linting/config.yaml +31 -0
- package/python/tools/windowed_edit_linting/install.sh +5 -0
- package/python/tools/windowed_edit_replace/bin/edit +172 -0
- package/python/tools/windowed_edit_replace/bin/insert +77 -0
- package/python/tools/windowed_edit_replace/config.yaml +60 -0
- package/python/tools/windowed_edit_replace/install.sh +5 -0
- package/python/tools/windowed_edit_rewrite/bin/edit +78 -0
- package/python/tools/windowed_edit_rewrite/config.yaml +11 -0
- package/python/tools/windowed_edit_rewrite/install.sh +5 -0
- package/python/trajectories/demonstrations/ctf/crypto/BabyEncryption.traj +318 -0
- package/python/trajectories/demonstrations/ctf/crypto/BabyTimeCapsule.traj +197 -0
- package/python/trajectories/demonstrations/ctf/crypto/eps.traj +289 -0
- package/python/trajectories/demonstrations/ctf/crypto/katy.traj +368 -0
- package/python/trajectories/demonstrations/ctf/forensics/flash.traj +102 -0
- package/python/trajectories/demonstrations/ctf/misc/networking_1.traj +102 -0
- package/python/trajectories/demonstrations/ctf/pwn/warmup.traj +159 -0
- package/python/trajectories/demonstrations/ctf/rev/rock.traj +251 -0
- package/python/trajectories/demonstrations/ctf/web/i_got_id_demo.traj +422 -0
- package/python/trajectories/demonstrations/function_calling_simple.traj +151 -0
- package/python/trajectories/demonstrations/human_thought__swe-bench-HumanEvalFix-python__lcb__t-0.00__p-0.95__c-4.00__install-0/humanevalfix-python-0.traj +129 -0
- package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__default__t-0.20__p-0.95__c-2.00__install-1___install_from_source/marshmallow-code__marshmallow-1867.traj +318 -0
- package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__default_sys-env_cursors_window100__t-0.20__p-0.95__c-2.00__install-1/marshmallow-code__marshmallow-1867.traj +251 -0
- package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__default_sys-env_window100__t-0.20__p-0.95__c-2.00__install-1/marshmallow-code__marshmallow-1867.traj +399 -0
- package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__function_calling__install-1/marshmallow-code__marshmallow-1867.traj +594 -0
- package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__function_calling_replace__install-1/marshmallow-code__marshmallow-1867.traj +592 -0
- package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__function_calling_replace_from_source/marshmallow-code__marshmallow-1867.traj +3316 -0
- package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__xml_sys-env_cursors_window100__t-0.20__p-0.95__c-2.00__install-1/marshmallow-code__marshmallow-1867.traj +251 -0
- package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__xml_sys-env_window100__t-0.20__p-0.95__c-2.00__install-1/marshmallow-code__marshmallow-1867.traj +399 -0
- package/python/trajectories/demonstrations/str_replace_anthropic_demo.yaml +432 -0
- package/rust/Cargo.toml +100 -0
- package/rust/README.md +49 -0
- package/rust/src/agent/action_sampler.rs +130 -0
- package/rust/src/agent/agents.rs +1029 -0
- package/rust/src/agent/history_processors.rs +277 -0
- package/rust/src/agent/hooks/mod.rs +208 -0
- package/rust/src/agent/mod.rs +24 -0
- package/rust/src/agent/models.rs +837 -0
- package/rust/src/agent/problem_statement.rs +355 -0
- package/rust/src/agent/reviewer.rs +505 -0
- package/rust/src/bin/sweagent.rs +784 -0
- package/rust/src/environment/deployment.rs +631 -0
- package/rust/src/environment/hooks/mod.rs +114 -0
- package/rust/src/environment/mod.rs +16 -0
- package/rust/src/environment/repo.rs +265 -0
- package/rust/src/environment/runtime.rs +237 -0
- package/rust/src/environment/swe_env.rs +248 -0
- package/rust/src/exceptions.rs +228 -0
- package/rust/src/lib.rs +68 -0
- package/rust/src/monitoring.rs +482 -0
- package/rust/src/run/hooks/mod.rs +134 -0
- package/rust/src/run/mod.rs +12 -0
- package/rust/src/run/run_batch.rs +563 -0
- package/rust/src/run/run_single.rs +196 -0
- package/rust/src/tools/bundle.rs +224 -0
- package/rust/src/tools/commands.rs +173 -0
- package/rust/src/tools/mod.rs +295 -0
- package/rust/src/tools/parsing.rs +354 -0
- package/rust/src/tools/registry.rs +143 -0
- package/rust/src/types.rs +554 -0
- package/rust/src/utils/config.rs +105 -0
- package/rust/src/utils/files.rs +137 -0
- package/rust/src/utils/github.rs +171 -0
- package/rust/src/utils/log.rs +65 -0
- package/rust/src/utils/mod.rs +17 -0
- package/rust/src/utils/serialization.rs +181 -0
- package/rust/src/utils/template.rs +173 -0
- package/typescript/README.md +335 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
tools:
|
|
2
|
+
open_site:
|
|
3
|
+
signature: "open_site <url>"
|
|
4
|
+
docstring: "Open the specified website URL or local file path"
|
|
5
|
+
arguments:
|
|
6
|
+
- name: url
|
|
7
|
+
type: string
|
|
8
|
+
description: "The URL to open (can be a web URL or file path)"
|
|
9
|
+
required: true
|
|
10
|
+
|
|
11
|
+
close_site:
|
|
12
|
+
signature: "close_site"
|
|
13
|
+
docstring: "Close the currently open browser window"
|
|
14
|
+
arguments: []
|
|
15
|
+
|
|
16
|
+
screenshot_site:
|
|
17
|
+
signature: "screenshot_site"
|
|
18
|
+
docstring: "Take a screenshot of the current page"
|
|
19
|
+
arguments: []
|
|
20
|
+
|
|
21
|
+
click_mouse:
|
|
22
|
+
signature: "click_mouse <x> <y> [<button>]"
|
|
23
|
+
docstring: "Click at the specified coordinates (shown as a red crosshair) on the current page"
|
|
24
|
+
arguments:
|
|
25
|
+
- name: x
|
|
26
|
+
type: integer
|
|
27
|
+
description: "X coordinate"
|
|
28
|
+
required: true
|
|
29
|
+
- name: y
|
|
30
|
+
type: integer
|
|
31
|
+
description: "Y coordinate"
|
|
32
|
+
required: true
|
|
33
|
+
- name: button
|
|
34
|
+
type: string
|
|
35
|
+
description: "Mouse button to click (left or right, default: left)"
|
|
36
|
+
required: false
|
|
37
|
+
enum: ["left", "right"]
|
|
38
|
+
|
|
39
|
+
double_click_mouse:
|
|
40
|
+
signature: "double_click_mouse <x> <y>"
|
|
41
|
+
docstring: "Double-click at the specified coordinates (shown as a red crosshair) on the current page"
|
|
42
|
+
arguments:
|
|
43
|
+
- name: x
|
|
44
|
+
type: integer
|
|
45
|
+
description: "X coordinate"
|
|
46
|
+
required: true
|
|
47
|
+
- name: y
|
|
48
|
+
type: integer
|
|
49
|
+
description: "Y coordinate"
|
|
50
|
+
required: true
|
|
51
|
+
|
|
52
|
+
move_mouse:
|
|
53
|
+
signature: "move_mouse <x> <y>"
|
|
54
|
+
docstring: "Move mouse to the specified coordinates (shown as a red crosshair) on the current page"
|
|
55
|
+
arguments:
|
|
56
|
+
- name: x
|
|
57
|
+
type: integer
|
|
58
|
+
description: "X coordinate"
|
|
59
|
+
required: true
|
|
60
|
+
- name: y
|
|
61
|
+
type: integer
|
|
62
|
+
description: "Y coordinate"
|
|
63
|
+
required: true
|
|
64
|
+
|
|
65
|
+
drag_mouse:
|
|
66
|
+
signature: "drag_mouse <path>"
|
|
67
|
+
docstring: "Drag mouse along a path (JSON format: [[x1,y1],[x2,y2],...]) on the current page"
|
|
68
|
+
arguments:
|
|
69
|
+
- name: path
|
|
70
|
+
type: string
|
|
71
|
+
description: "JSON array of coordinate pairs for the drag path (e.g., '[[0,0],[100,100]]')"
|
|
72
|
+
required: true
|
|
73
|
+
|
|
74
|
+
type_text:
|
|
75
|
+
signature: "type_text <text>"
|
|
76
|
+
docstring: "Type the given text at the current focused element on the current page"
|
|
77
|
+
arguments:
|
|
78
|
+
- name: text
|
|
79
|
+
type: string
|
|
80
|
+
description: "Text to type"
|
|
81
|
+
required: true
|
|
82
|
+
|
|
83
|
+
scroll_on_page:
|
|
84
|
+
signature: "scroll_on_page <scroll_x> <scroll_y>"
|
|
85
|
+
docstring: "Scroll by the specified number of pixels on the current page"
|
|
86
|
+
arguments:
|
|
87
|
+
- name: scroll_x
|
|
88
|
+
type: integer
|
|
89
|
+
description: "Horizontal scroll amount in pixels"
|
|
90
|
+
required: true
|
|
91
|
+
- name: scroll_y
|
|
92
|
+
type: integer
|
|
93
|
+
description: "Vertical scroll amount in pixels"
|
|
94
|
+
required: true
|
|
95
|
+
|
|
96
|
+
execute_script_on_page:
|
|
97
|
+
signature: "execute_script_on_page <script>"
|
|
98
|
+
docstring: "Execute a custom JavaScript code snippet on the current page"
|
|
99
|
+
arguments:
|
|
100
|
+
- name: script
|
|
101
|
+
type: string
|
|
102
|
+
description: "JavaScript code to execute"
|
|
103
|
+
required: true
|
|
104
|
+
|
|
105
|
+
navigate_back:
|
|
106
|
+
signature: "navigate_back"
|
|
107
|
+
docstring: "Navigate back in the browser history"
|
|
108
|
+
arguments: []
|
|
109
|
+
|
|
110
|
+
navigate_forward:
|
|
111
|
+
signature: "navigate_forward"
|
|
112
|
+
docstring: "Navigate forward in the browser history"
|
|
113
|
+
arguments: []
|
|
114
|
+
|
|
115
|
+
reload_page:
|
|
116
|
+
signature: "reload_page"
|
|
117
|
+
docstring: "Reload the current webpage"
|
|
118
|
+
arguments: []
|
|
119
|
+
|
|
120
|
+
wait_time:
|
|
121
|
+
signature: "wait_time <ms>"
|
|
122
|
+
docstring: "Wait for the specified number of milliseconds"
|
|
123
|
+
arguments:
|
|
124
|
+
- name: ms
|
|
125
|
+
type: integer
|
|
126
|
+
description: "Time to wait in milliseconds"
|
|
127
|
+
required: true
|
|
128
|
+
|
|
129
|
+
press_keys_on_page:
|
|
130
|
+
signature: "press_keys_on_page <keys>"
|
|
131
|
+
docstring: "Press the specified keys (JSON format: [\"key1\", \"key2\"]) on the current page"
|
|
132
|
+
arguments:
|
|
133
|
+
- name: keys
|
|
134
|
+
type: string
|
|
135
|
+
description: "JSON array of keys to press (e.g., '[\"ctrl\", \"c\"]')"
|
|
136
|
+
required: true
|
|
137
|
+
|
|
138
|
+
set_browser_window_size:
|
|
139
|
+
signature: "set_browser_window_size <width> <height>"
|
|
140
|
+
docstring: "Set the browser window size to the specified dimensions"
|
|
141
|
+
arguments:
|
|
142
|
+
- name: width
|
|
143
|
+
type: integer
|
|
144
|
+
description: "Window width in pixels"
|
|
145
|
+
required: true
|
|
146
|
+
- name: height
|
|
147
|
+
type: integer
|
|
148
|
+
description: "Window height in pixels"
|
|
149
|
+
required: true
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
get_console_output:
|
|
153
|
+
signature: "get_console_output"
|
|
154
|
+
docstring: "Get console output messages from the browser (logs, errors, warnings, etc.)"
|
|
155
|
+
arguments: []
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
/root/python3.11/bin/python3 -m pip install flask requests playwright
|
|
4
|
+
/root/python3.11/bin/python3 -m playwright install-deps chromium
|
|
5
|
+
|
|
6
|
+
if [ -f /usr/bin/google-chrome ]; then
|
|
7
|
+
export WEB_BROWSER_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome
|
|
8
|
+
elif [ -f /usr/bin/chromium ]; then
|
|
9
|
+
export WEB_BROWSER_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium
|
|
10
|
+
elif [ -f /usr/bin/google-chrome-stable ]; then
|
|
11
|
+
export WEB_BROWSER_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable
|
|
12
|
+
else
|
|
13
|
+
/root/python3.11/bin/python3 -m playwright install chromium
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
export WEB_BROWSER_SCREENSHOT_MODE=print
|
|
17
|
+
|
|
18
|
+
export WEB_BROWSER_PORT=19321
|
|
19
|
+
|
|
20
|
+
mkdir -p /root/.web_browser_logs
|
|
21
|
+
|
|
22
|
+
run_web_browser_server &> /root/.web_browser_logs/web-browser-server.log &
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import contextlib
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from playwright.sync_api import Browser, Page, Playwright, sync_playwright
|
|
11
|
+
from web_browser_config import ServerConfig
|
|
12
|
+
|
|
13
|
+
config = ServerConfig()
|
|
14
|
+
|
|
15
|
+
SUPPORTED_BROWSERS = {"chromium", "firefox"}
|
|
16
|
+
|
|
17
|
+
CROSSHAIR_JS = """
|
|
18
|
+
([x, y, id]) => {
|
|
19
|
+
const size = 20;
|
|
20
|
+
const thickness = 3;
|
|
21
|
+
const hId = id + '_h';
|
|
22
|
+
const vId = id + '_v';
|
|
23
|
+
|
|
24
|
+
const createLine = (elementId, styles) => {
|
|
25
|
+
let line = document.getElementById(elementId);
|
|
26
|
+
if (!line) {
|
|
27
|
+
line = document.createElement('div');
|
|
28
|
+
line.id = elementId;
|
|
29
|
+
Object.assign(line.style, {
|
|
30
|
+
position: 'fixed',
|
|
31
|
+
pointerEvents: 'none',
|
|
32
|
+
zIndex: '2147483647',
|
|
33
|
+
backgroundColor: 'red',
|
|
34
|
+
boxSizing: 'border-box',
|
|
35
|
+
margin: '0',
|
|
36
|
+
padding: '0',
|
|
37
|
+
border: 'none',
|
|
38
|
+
outline: 'none',
|
|
39
|
+
transform: 'translateZ(0)',
|
|
40
|
+
...styles
|
|
41
|
+
});
|
|
42
|
+
document.body.appendChild(line);
|
|
43
|
+
}
|
|
44
|
+
return line;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const hLine = createLine(hId, {
|
|
48
|
+
width: `${size}px`,
|
|
49
|
+
height: `${thickness}px`,
|
|
50
|
+
left: `${Math.round(x - size / 2)}px`,
|
|
51
|
+
top: `${Math.round(y - thickness / 2)}px`
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const vLine = createLine(vId, {
|
|
55
|
+
width: `${thickness}px`,
|
|
56
|
+
height: `${size}px`,
|
|
57
|
+
left: `${Math.round(x - thickness / 2)}px`,
|
|
58
|
+
top: `${Math.round(y - size / 2)}px`
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
REMOVE_CROSSHAIR_JS = """
|
|
65
|
+
(id) => {
|
|
66
|
+
const hEl = document.getElementById(id + '_h');
|
|
67
|
+
const vEl = document.getElementById(id + '_v');
|
|
68
|
+
if (hEl) hEl.remove();
|
|
69
|
+
if (vEl) vEl.remove();
|
|
70
|
+
}
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
KEY_MAP = {
|
|
75
|
+
# Function keys
|
|
76
|
+
"f1": "F1",
|
|
77
|
+
"f2": "F2",
|
|
78
|
+
"f3": "F3",
|
|
79
|
+
"f4": "F4",
|
|
80
|
+
"f5": "F5",
|
|
81
|
+
"f6": "F6",
|
|
82
|
+
"f7": "F7",
|
|
83
|
+
"f8": "F8",
|
|
84
|
+
"f9": "F9",
|
|
85
|
+
"f10": "F10",
|
|
86
|
+
"f11": "F11",
|
|
87
|
+
"f12": "F12",
|
|
88
|
+
# Number keys (top row)
|
|
89
|
+
"0": "Digit0",
|
|
90
|
+
"1": "Digit1",
|
|
91
|
+
"2": "Digit2",
|
|
92
|
+
"3": "Digit3",
|
|
93
|
+
"4": "Digit4",
|
|
94
|
+
"5": "Digit5",
|
|
95
|
+
"6": "Digit6",
|
|
96
|
+
"7": "Digit7",
|
|
97
|
+
"8": "Digit8",
|
|
98
|
+
"9": "Digit9",
|
|
99
|
+
# Letter keys
|
|
100
|
+
"a": "KeyA",
|
|
101
|
+
"b": "KeyB",
|
|
102
|
+
"c": "KeyC",
|
|
103
|
+
"d": "KeyD",
|
|
104
|
+
"e": "KeyE",
|
|
105
|
+
"f": "KeyF",
|
|
106
|
+
"g": "KeyG",
|
|
107
|
+
"h": "KeyH",
|
|
108
|
+
"i": "KeyI",
|
|
109
|
+
"j": "KeyJ",
|
|
110
|
+
"k": "KeyK",
|
|
111
|
+
"l": "KeyL",
|
|
112
|
+
"m": "KeyM",
|
|
113
|
+
"n": "KeyN",
|
|
114
|
+
"o": "KeyO",
|
|
115
|
+
"p": "KeyP",
|
|
116
|
+
"q": "KeyQ",
|
|
117
|
+
"r": "KeyR",
|
|
118
|
+
"s": "KeyS",
|
|
119
|
+
"t": "KeyT",
|
|
120
|
+
"u": "KeyU",
|
|
121
|
+
"v": "KeyV",
|
|
122
|
+
"w": "KeyW",
|
|
123
|
+
"x": "KeyX",
|
|
124
|
+
"y": "KeyY",
|
|
125
|
+
"z": "KeyZ",
|
|
126
|
+
# Arrow keys
|
|
127
|
+
"up": "ArrowUp",
|
|
128
|
+
"down": "ArrowDown",
|
|
129
|
+
"left": "ArrowLeft",
|
|
130
|
+
"right": "ArrowRight",
|
|
131
|
+
"arrow_up": "ArrowUp",
|
|
132
|
+
"arrow_down": "ArrowDown",
|
|
133
|
+
"arrow_left": "ArrowLeft",
|
|
134
|
+
"arrow_right": "ArrowRight",
|
|
135
|
+
# Navigation keys
|
|
136
|
+
"home": "Home",
|
|
137
|
+
"end": "End",
|
|
138
|
+
"page_up": "PageUp",
|
|
139
|
+
"page_down": "PageDown",
|
|
140
|
+
"pageup": "PageUp",
|
|
141
|
+
"pagedown": "PageDown",
|
|
142
|
+
# Editing keys
|
|
143
|
+
"backspace": "Backspace",
|
|
144
|
+
"delete": "Delete",
|
|
145
|
+
"insert": "Insert",
|
|
146
|
+
"enter": "Enter",
|
|
147
|
+
"return": "Enter",
|
|
148
|
+
"tab": "Tab",
|
|
149
|
+
"escape": "Escape",
|
|
150
|
+
"esc": "Escape",
|
|
151
|
+
# Modifier keys
|
|
152
|
+
"shift": "Shift",
|
|
153
|
+
"ctrl": "Control",
|
|
154
|
+
"control": "Control",
|
|
155
|
+
"alt": "Alt",
|
|
156
|
+
"meta": "Meta",
|
|
157
|
+
"shift_left": "ShiftLeft",
|
|
158
|
+
"ctrl_or_meta": "ControlOrMeta",
|
|
159
|
+
"control_or_meta": "ControlOrMeta",
|
|
160
|
+
# Punctuation and symbols
|
|
161
|
+
"space": " ",
|
|
162
|
+
"spacebar": " ",
|
|
163
|
+
"backquote": "Backquote",
|
|
164
|
+
"`": "Backquote",
|
|
165
|
+
"backtick": "Backquote",
|
|
166
|
+
"minus": "Minus",
|
|
167
|
+
"-": "Minus",
|
|
168
|
+
"dash": "Minus",
|
|
169
|
+
"equal": "Equal",
|
|
170
|
+
"=": "Equal",
|
|
171
|
+
"equals": "Equal",
|
|
172
|
+
"backslash": "Backslash",
|
|
173
|
+
"\\": "Backslash",
|
|
174
|
+
"bracket_left": "BracketLeft",
|
|
175
|
+
"[": "BracketLeft",
|
|
176
|
+
"bracket_right": "BracketRight",
|
|
177
|
+
"]": "BracketRight",
|
|
178
|
+
"semicolon": "Semicolon",
|
|
179
|
+
";": "Semicolon",
|
|
180
|
+
"quote": "Quote",
|
|
181
|
+
"'": "Quote",
|
|
182
|
+
"apostrophe": "Quote",
|
|
183
|
+
"comma": "Comma",
|
|
184
|
+
",": "Comma",
|
|
185
|
+
"period": "Period",
|
|
186
|
+
".": "Period",
|
|
187
|
+
"dot": "Period",
|
|
188
|
+
"slash": "Slash",
|
|
189
|
+
"/": "Slash",
|
|
190
|
+
# Numpad keys
|
|
191
|
+
"numpad_0": "Numpad0",
|
|
192
|
+
"numpad_1": "Numpad1",
|
|
193
|
+
"numpad_2": "Numpad2",
|
|
194
|
+
"numpad_3": "Numpad3",
|
|
195
|
+
"numpad_4": "Numpad4",
|
|
196
|
+
"numpad_5": "Numpad5",
|
|
197
|
+
"numpad_6": "Numpad6",
|
|
198
|
+
"numpad_7": "Numpad7",
|
|
199
|
+
"numpad_8": "Numpad8",
|
|
200
|
+
"numpad_9": "Numpad9",
|
|
201
|
+
"numpad_add": "NumpadAdd",
|
|
202
|
+
"numpad_subtract": "NumpadSubtract",
|
|
203
|
+
"numpad_multiply": "NumpadMultiply",
|
|
204
|
+
"numpad_divide": "NumpadDivide",
|
|
205
|
+
"numpad_decimal": "NumpadDecimal",
|
|
206
|
+
"numpad_enter": "NumpadEnter",
|
|
207
|
+
# Lock keys
|
|
208
|
+
"caps_lock": "CapsLock",
|
|
209
|
+
"capslock": "CapsLock",
|
|
210
|
+
"num_lock": "NumLock",
|
|
211
|
+
"numlock": "NumLock",
|
|
212
|
+
"scroll_lock": "ScrollLock",
|
|
213
|
+
"scrolllock": "ScrollLock",
|
|
214
|
+
# Common combinations (case-insensitive aliases)
|
|
215
|
+
"ENTER": "Enter",
|
|
216
|
+
"ESCAPE": "Escape",
|
|
217
|
+
"BACKSPACE": "Backspace",
|
|
218
|
+
"DELETE": "Delete",
|
|
219
|
+
"TAB": "Tab",
|
|
220
|
+
"SPACE": " ",
|
|
221
|
+
"UP": "ArrowUp",
|
|
222
|
+
"DOWN": "ArrowDown",
|
|
223
|
+
"LEFT": "ArrowLeft",
|
|
224
|
+
"RIGHT": "ArrowRight",
|
|
225
|
+
"HOME": "Home",
|
|
226
|
+
"END": "End",
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class BrowserManager:
|
|
231
|
+
"""Manages Playwright browser instance with proper resource cleanup."""
|
|
232
|
+
|
|
233
|
+
def __init__(self):
|
|
234
|
+
self.headless = config.headless
|
|
235
|
+
self.browser_type = self._validate_browser_type(config.browser_type)
|
|
236
|
+
self.page: Page | None = None
|
|
237
|
+
self.screenshot_index = 0
|
|
238
|
+
self.mouse_x = 0
|
|
239
|
+
self.mouse_y = 0
|
|
240
|
+
self._lock = threading.RLock()
|
|
241
|
+
self.window_width = config.window_width
|
|
242
|
+
self.window_height = config.window_height
|
|
243
|
+
self.screenshot_delay = config.screenshot_delay
|
|
244
|
+
self.reconnect_timeout = config.reconnect_timeout
|
|
245
|
+
self.crosshair_id = config.crosshair_id
|
|
246
|
+
self.console_messages: list[dict[str, Any]] = []
|
|
247
|
+
self._init_browser()
|
|
248
|
+
|
|
249
|
+
def _validate_browser_type(self, browser_type: str) -> str:
|
|
250
|
+
"""Validate and return the browser type."""
|
|
251
|
+
browser_type = browser_type.lower()
|
|
252
|
+
if browser_type not in SUPPORTED_BROWSERS:
|
|
253
|
+
msg = (
|
|
254
|
+
f"Unsupported browser type: {browser_type}. Supported browsers: {', '.join(sorted(SUPPORTED_BROWSERS))}"
|
|
255
|
+
)
|
|
256
|
+
raise ValueError(msg)
|
|
257
|
+
return browser_type
|
|
258
|
+
|
|
259
|
+
def _init_browser(self):
|
|
260
|
+
self.playwright: Playwright = sync_playwright().start()
|
|
261
|
+
browser_launcher = getattr(self.playwright, self.browser_type)
|
|
262
|
+
executable_path = None
|
|
263
|
+
if self.browser_type == "chromium" and config.chromium_executable_path:
|
|
264
|
+
executable_path = config.chromium_executable_path
|
|
265
|
+
elif self.browser_type == "firefox" and config.firefox_executable_path:
|
|
266
|
+
executable_path = config.firefox_executable_path
|
|
267
|
+
launch_options = {"headless": self.headless}
|
|
268
|
+
if executable_path and Path(executable_path).exists():
|
|
269
|
+
launch_options["executable_path"] = executable_path
|
|
270
|
+
elif executable_path:
|
|
271
|
+
print(f"Warning: Executable path '{executable_path}' does not exist, using default browser")
|
|
272
|
+
self.browser: Browser = browser_launcher.launch(**launch_options)
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def browser_name(self) -> str:
|
|
276
|
+
"""Get the name of the browser."""
|
|
277
|
+
return self.browser.browser_type.name
|
|
278
|
+
|
|
279
|
+
@contextlib.contextmanager
|
|
280
|
+
def _browser_lock(self):
|
|
281
|
+
"""Context manager for thread-safe browser operations."""
|
|
282
|
+
with self._lock:
|
|
283
|
+
yield self._ensure_browser()
|
|
284
|
+
|
|
285
|
+
def _ensure_browser(self) -> Page:
|
|
286
|
+
"""Launch Chromium lazily and move cursor to (0,0) once."""
|
|
287
|
+
if self.page is not None:
|
|
288
|
+
return self.page
|
|
289
|
+
ctx = self.browser.new_context(viewport={"width": self.window_width, "height": self.window_height})
|
|
290
|
+
self.page = ctx.new_page()
|
|
291
|
+
self._setup_console_listener(self.page)
|
|
292
|
+
self.page.mouse.move(0, 0)
|
|
293
|
+
self.mouse_x = self.mouse_y = 0
|
|
294
|
+
return self.page
|
|
295
|
+
|
|
296
|
+
def is_website_open(self) -> bool:
|
|
297
|
+
"""Check if a website is currently open."""
|
|
298
|
+
return self.page is not None and self.page.url not in (None, "about:blank", "")
|
|
299
|
+
|
|
300
|
+
def cleanup(self):
|
|
301
|
+
"""Clean up browser resources safely."""
|
|
302
|
+
with self._lock:
|
|
303
|
+
for resource, name in [(self.page, "page"), (self.browser, "browser"), (self.playwright, "playwright")]:
|
|
304
|
+
if resource is not None:
|
|
305
|
+
try:
|
|
306
|
+
if name == "playwright":
|
|
307
|
+
resource.stop()
|
|
308
|
+
else:
|
|
309
|
+
resource.close()
|
|
310
|
+
except Exception:
|
|
311
|
+
pass # Ignore cleanup errors
|
|
312
|
+
|
|
313
|
+
self.browser = self.page = self.playwright = None
|
|
314
|
+
|
|
315
|
+
def _inject_crosshair(self, page: Page, x: int, y: int) -> bool:
|
|
316
|
+
"""Inject crosshair at given coordinates. Returns True if successful."""
|
|
317
|
+
try:
|
|
318
|
+
page.evaluate(CROSSHAIR_JS, [x, y, self.crosshair_id])
|
|
319
|
+
return True
|
|
320
|
+
except Exception:
|
|
321
|
+
return False
|
|
322
|
+
|
|
323
|
+
def _remove_crosshair(self, page: Page) -> None:
|
|
324
|
+
"""Remove crosshair elements from the page."""
|
|
325
|
+
try:
|
|
326
|
+
page.evaluate(REMOVE_CROSSHAIR_JS, self.crosshair_id)
|
|
327
|
+
except Exception:
|
|
328
|
+
pass
|
|
329
|
+
|
|
330
|
+
def take_screenshot(self) -> dict[str, Any]:
|
|
331
|
+
"""Capture screenshot with crosshair."""
|
|
332
|
+
with self._browser_lock() as page:
|
|
333
|
+
# this retry logic is a hack to ensure the page is loaded
|
|
334
|
+
# (at least enough to inject the crosshair)
|
|
335
|
+
timeout = time.time() + self.reconnect_timeout
|
|
336
|
+
while time.time() < timeout:
|
|
337
|
+
if self._inject_crosshair(page, self.mouse_x, self.mouse_y):
|
|
338
|
+
break
|
|
339
|
+
time.sleep(max(0.3, self.screenshot_delay))
|
|
340
|
+
time.sleep(self.screenshot_delay)
|
|
341
|
+
screenshot_data = page.screenshot(type="png")
|
|
342
|
+
self._remove_crosshair(page)
|
|
343
|
+
self.screenshot_index += 1
|
|
344
|
+
return {
|
|
345
|
+
"screenshot": base64.b64encode(screenshot_data).decode(),
|
|
346
|
+
"screenshot_index": self.screenshot_index,
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
def validate_coordinates(self, x: int, y: int) -> tuple[bool, bool]:
|
|
350
|
+
x_is_valid = 0 <= x <= self.window_width
|
|
351
|
+
y_is_valid = 0 <= y <= self.window_height
|
|
352
|
+
return (x_is_valid, y_is_valid)
|
|
353
|
+
|
|
354
|
+
def constrain_mouse_position(self, page: Page) -> bool:
|
|
355
|
+
"""Constrain mouse position to window bounds and move if needed."""
|
|
356
|
+
if self.window_width <= 0 or self.window_height <= 0:
|
|
357
|
+
return False
|
|
358
|
+
if self.mouse_x >= self.window_width or self.mouse_y >= self.window_height:
|
|
359
|
+
self.mouse_x = min(self.mouse_x, self.window_width - 1)
|
|
360
|
+
self.mouse_y = min(self.mouse_y, self.window_height - 1)
|
|
361
|
+
self.mouse_x = max(0, self.mouse_x)
|
|
362
|
+
self.mouse_y = max(0, self.mouse_y)
|
|
363
|
+
page.mouse.move(self.mouse_x, self.mouse_y)
|
|
364
|
+
return True
|
|
365
|
+
return False
|
|
366
|
+
|
|
367
|
+
def get_key(self, key: str) -> str:
|
|
368
|
+
"""Get the key from the key map."""
|
|
369
|
+
if key.lower() in KEY_MAP:
|
|
370
|
+
return KEY_MAP[key.lower()]
|
|
371
|
+
msg = f"Key {key} not found. Supported keys: {', '.join(KEY_MAP.keys())}"
|
|
372
|
+
raise ValueError(msg)
|
|
373
|
+
|
|
374
|
+
def key_down(self, key: str):
|
|
375
|
+
"""Press and hold a key."""
|
|
376
|
+
playwright_key = self.get_key(key)
|
|
377
|
+
self.page.keyboard.down(playwright_key)
|
|
378
|
+
|
|
379
|
+
def key_press(self, key: str):
|
|
380
|
+
"""Press a key."""
|
|
381
|
+
playwright_key = self.get_key(key)
|
|
382
|
+
self.page.keyboard.press(playwright_key)
|
|
383
|
+
|
|
384
|
+
def key_up(self, key: str):
|
|
385
|
+
"""Release a key."""
|
|
386
|
+
playwright_key = self.get_key(key)
|
|
387
|
+
self.page.keyboard.up(playwright_key)
|
|
388
|
+
|
|
389
|
+
def _setup_console_listener(self, page: Page):
|
|
390
|
+
"""Set up console message listener for the page."""
|
|
391
|
+
|
|
392
|
+
def on_console(msg):
|
|
393
|
+
self.console_messages.append(
|
|
394
|
+
{"type": msg.type, "text": msg.text, "timestamp": time.time(), "location": msg.location}
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
page.on("console", on_console)
|
|
398
|
+
|
|
399
|
+
def get_console_output(self) -> list[dict[str, Any]]:
|
|
400
|
+
"""Get all console messages and clear the buffer."""
|
|
401
|
+
with self._lock:
|
|
402
|
+
messages = self.console_messages.copy()
|
|
403
|
+
self.console_messages.clear()
|
|
404
|
+
return messages
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from web_browser_utils import ScreenshotMode
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class ClientConfig:
|
|
11
|
+
"""Configuration for the web_browser client"""
|
|
12
|
+
|
|
13
|
+
port: int = int(os.getenv("WEB_BROWSER_PORT", "8009"))
|
|
14
|
+
autoscreenshot: bool = os.getenv("WEB_BROWSER_AUTOSCREENSHOT", "1") == "1"
|
|
15
|
+
screenshot_mode: ScreenshotMode = ScreenshotMode(
|
|
16
|
+
os.getenv("WEB_BROWSER_SCREENSHOT_MODE", ScreenshotMode.SAVE.value)
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ServerConfig:
|
|
22
|
+
"""Configuration for the web_browser server"""
|
|
23
|
+
|
|
24
|
+
port: int = int(os.getenv("WEB_BROWSER_PORT", "8009"))
|
|
25
|
+
window_width: int = int(os.getenv("WEB_BROWSER_WINDOW_WIDTH", 1024))
|
|
26
|
+
window_height: int = int(os.getenv("WEB_BROWSER_WINDOW_HEIGHT", 768))
|
|
27
|
+
headless: bool = os.getenv("WEB_BROWSER_HEADLESS", "1") != "0"
|
|
28
|
+
screenshot_delay: float = float(os.getenv("WEB_BROWSER_SCREENSHOT_DELAY", 0.2))
|
|
29
|
+
browser_type: str = os.getenv("WEB_BROWSER_BROWSER_TYPE", "chromium")
|
|
30
|
+
reconnect_timeout: float = float(os.getenv("WEB_BROWSER_RECONNECT_TIMEOUT", 15))
|
|
31
|
+
chromium_executable_path: str | None = os.getenv("WEB_BROWSER_CHROMIUM_EXECUTABLE_PATH")
|
|
32
|
+
firefox_executable_path: str | None = os.getenv("WEB_BROWSER_FIREFOX_EXECUTABLE_PATH")
|
|
33
|
+
crosshair_id: str = "__web_browser_crosshair__"
|