@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,105 @@
|
|
|
1
|
+
//! Configuration utilities for SWE-agent
|
|
2
|
+
|
|
3
|
+
use crate::exceptions::{Result, SWEAgentError};
|
|
4
|
+
use std::collections::HashMap;
|
|
5
|
+
use std::env;
|
|
6
|
+
use std::path::{Path, PathBuf};
|
|
7
|
+
|
|
8
|
+
/// Load environment variables from a .env file
|
|
9
|
+
pub fn load_environment_variables(path: Option<&Path>) -> Result<()> {
|
|
10
|
+
if let Some(p) = path {
|
|
11
|
+
if p.exists() {
|
|
12
|
+
dotenvy::from_path(p).map_err(|e| {
|
|
13
|
+
SWEAgentError::ConfigurationError(format!("Failed to load env file: {}", e))
|
|
14
|
+
})?;
|
|
15
|
+
}
|
|
16
|
+
} else {
|
|
17
|
+
// Try to load from default locations
|
|
18
|
+
let _ = dotenvy::dotenv();
|
|
19
|
+
}
|
|
20
|
+
Ok(())
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Convert a path relative to repo root to absolute path
|
|
24
|
+
pub fn convert_path_relative_to_repo_root(path: &str, repo_root: &Path) -> PathBuf {
|
|
25
|
+
if Path::new(path).is_absolute() {
|
|
26
|
+
PathBuf::from(path)
|
|
27
|
+
} else {
|
|
28
|
+
repo_root.join(path)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Convert a path to absolute path
|
|
33
|
+
pub fn convert_path_to_abspath(path: &str, base: &Path) -> PathBuf {
|
|
34
|
+
let p = Path::new(path);
|
|
35
|
+
if p.is_absolute() {
|
|
36
|
+
p.to_path_buf()
|
|
37
|
+
} else {
|
|
38
|
+
base.join(p)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Strip absolute path prefixes from dictionary values
|
|
43
|
+
pub fn strip_abspath_from_dict(
|
|
44
|
+
dict: &HashMap<String, String>,
|
|
45
|
+
prefix: &str,
|
|
46
|
+
) -> HashMap<String, String> {
|
|
47
|
+
dict.iter()
|
|
48
|
+
.map(|(k, v)| {
|
|
49
|
+
let new_v = if v.starts_with(prefix) {
|
|
50
|
+
v.strip_prefix(prefix).unwrap_or(v).to_string()
|
|
51
|
+
} else {
|
|
52
|
+
v.clone()
|
|
53
|
+
};
|
|
54
|
+
(k.clone(), new_v)
|
|
55
|
+
})
|
|
56
|
+
.collect()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Check if a string could be a file path
|
|
60
|
+
pub fn could_be_a_path(s: &str) -> bool {
|
|
61
|
+
// Check for common path indicators
|
|
62
|
+
s.contains('/') || s.contains('\\') || s.starts_with('.') || s.contains('.')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Get an environment variable with optional default
|
|
66
|
+
pub fn get_env_var(name: &str, default: Option<&str>) -> Option<String> {
|
|
67
|
+
env::var(name).ok().or_else(|| default.map(String::from))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Get an API key from environment
|
|
71
|
+
pub fn get_api_key(provider: &str) -> Option<String> {
|
|
72
|
+
let key_name = format!("{}_API_KEY", provider.to_uppercase().replace('-', "_"));
|
|
73
|
+
env::var(&key_name).ok()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Parse API keys that may be separated by :::
|
|
77
|
+
pub fn parse_api_keys(key_string: &str) -> Vec<String> {
|
|
78
|
+
key_string
|
|
79
|
+
.split(":::")
|
|
80
|
+
.map(|s| s.trim().to_string())
|
|
81
|
+
.filter(|s| !s.is_empty())
|
|
82
|
+
.collect()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#[cfg(test)]
|
|
86
|
+
mod tests {
|
|
87
|
+
use super::*;
|
|
88
|
+
|
|
89
|
+
#[test]
|
|
90
|
+
fn test_could_be_a_path() {
|
|
91
|
+
assert!(could_be_a_path("/home/user/file.txt"));
|
|
92
|
+
assert!(could_be_a_path("./relative/path"));
|
|
93
|
+
assert!(could_be_a_path("file.py"));
|
|
94
|
+
assert!(!could_be_a_path("simple_string"));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#[test]
|
|
98
|
+
fn test_parse_api_keys() {
|
|
99
|
+
let keys = parse_api_keys("key1:::key2:::key3");
|
|
100
|
+
assert_eq!(keys, vec!["key1", "key2", "key3"]);
|
|
101
|
+
|
|
102
|
+
let single = parse_api_keys("single_key");
|
|
103
|
+
assert_eq!(single, vec!["single_key"]);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
//! File utilities for SWE-agent
|
|
2
|
+
|
|
3
|
+
use crate::exceptions::{Result, SWEAgentError};
|
|
4
|
+
use std::fs;
|
|
5
|
+
use std::path::Path;
|
|
6
|
+
|
|
7
|
+
/// Load a file's contents as a string
|
|
8
|
+
pub fn load_file(path: &Path) -> Result<String> {
|
|
9
|
+
fs::read_to_string(path)
|
|
10
|
+
.map_err(|e| SWEAgentError::FileNotFound(format!("{}: {}", path.display(), e)))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/// Write contents to a file
|
|
14
|
+
pub fn write_file(path: &Path, contents: &str) -> Result<()> {
|
|
15
|
+
if let Some(parent) = path.parent() {
|
|
16
|
+
fs::create_dir_all(parent)?;
|
|
17
|
+
}
|
|
18
|
+
fs::write(path, contents)?;
|
|
19
|
+
Ok(())
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Check if a file exists
|
|
23
|
+
pub fn file_exists(path: &Path) -> bool {
|
|
24
|
+
path.exists() && path.is_file()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Check if a directory exists
|
|
28
|
+
pub fn dir_exists(path: &Path) -> bool {
|
|
29
|
+
path.exists() && path.is_dir()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Create a directory and all parent directories
|
|
33
|
+
pub fn ensure_dir(path: &Path) -> Result<()> {
|
|
34
|
+
fs::create_dir_all(path)?;
|
|
35
|
+
Ok(())
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Get the extension of a file
|
|
39
|
+
pub fn get_extension(path: &Path) -> Option<String> {
|
|
40
|
+
path.extension().and_then(|e| e.to_str()).map(String::from)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Read a JSON file and deserialize it
|
|
44
|
+
pub fn load_json<T: serde::de::DeserializeOwned>(path: &Path) -> Result<T> {
|
|
45
|
+
let contents = load_file(path)?;
|
|
46
|
+
serde_json::from_str(&contents).map_err(|e| {
|
|
47
|
+
SWEAgentError::SerializationError(format!(
|
|
48
|
+
"Failed to parse JSON from {}: {}",
|
|
49
|
+
path.display(),
|
|
50
|
+
e
|
|
51
|
+
))
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Read a YAML file and deserialize it
|
|
56
|
+
pub fn load_yaml<T: serde::de::DeserializeOwned>(path: &Path) -> Result<T> {
|
|
57
|
+
let contents = load_file(path)?;
|
|
58
|
+
serde_yaml::from_str(&contents).map_err(|e| {
|
|
59
|
+
SWEAgentError::SerializationError(format!(
|
|
60
|
+
"Failed to parse YAML from {}: {}",
|
|
61
|
+
path.display(),
|
|
62
|
+
e
|
|
63
|
+
))
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Save an object as JSON to a file
|
|
68
|
+
pub fn save_json<T: serde::Serialize>(path: &Path, data: &T) -> Result<()> {
|
|
69
|
+
let contents = serde_json::to_string_pretty(data)?;
|
|
70
|
+
write_file(path, &contents)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Save an object as YAML to a file
|
|
74
|
+
pub fn save_yaml<T: serde::Serialize>(path: &Path, data: &T) -> Result<()> {
|
|
75
|
+
let contents = serde_yaml::to_string(data)?;
|
|
76
|
+
write_file(path, &contents)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Find files matching a glob pattern
|
|
80
|
+
pub fn find_files(base_dir: &Path, pattern: &str) -> Result<Vec<std::path::PathBuf>> {
|
|
81
|
+
let full_pattern = base_dir.join(pattern);
|
|
82
|
+
let pattern_str = full_pattern.to_string_lossy();
|
|
83
|
+
|
|
84
|
+
glob::glob(&pattern_str)
|
|
85
|
+
.map_err(|e| SWEAgentError::IoError(format!("Invalid glob pattern: {}", e)))?
|
|
86
|
+
.filter_map(|entry| entry.ok())
|
|
87
|
+
.collect::<Vec<_>>()
|
|
88
|
+
.pipe(Ok)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
trait Pipe: Sized {
|
|
92
|
+
fn pipe<T, F: FnOnce(Self) -> T>(self, f: F) -> T {
|
|
93
|
+
f(self)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
impl<T> Pipe for T {}
|
|
98
|
+
|
|
99
|
+
#[cfg(test)]
|
|
100
|
+
mod tests {
|
|
101
|
+
use super::*;
|
|
102
|
+
use tempfile::TempDir;
|
|
103
|
+
|
|
104
|
+
#[test]
|
|
105
|
+
fn test_write_and_load_file() {
|
|
106
|
+
let dir = TempDir::new().unwrap();
|
|
107
|
+
let path = dir.path().join("test.txt");
|
|
108
|
+
|
|
109
|
+
write_file(&path, "hello world").unwrap();
|
|
110
|
+
let contents = load_file(&path).unwrap();
|
|
111
|
+
|
|
112
|
+
assert_eq!(contents, "hello world");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#[test]
|
|
116
|
+
fn test_file_exists() {
|
|
117
|
+
let dir = TempDir::new().unwrap();
|
|
118
|
+
let path = dir.path().join("test.txt");
|
|
119
|
+
|
|
120
|
+
assert!(!file_exists(&path));
|
|
121
|
+
write_file(&path, "test").unwrap();
|
|
122
|
+
assert!(file_exists(&path));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#[test]
|
|
126
|
+
fn test_get_extension() {
|
|
127
|
+
assert_eq!(
|
|
128
|
+
get_extension(Path::new("file.txt")),
|
|
129
|
+
Some("txt".to_string())
|
|
130
|
+
);
|
|
131
|
+
assert_eq!(
|
|
132
|
+
get_extension(Path::new("file.tar.gz")),
|
|
133
|
+
Some("gz".to_string())
|
|
134
|
+
);
|
|
135
|
+
assert_eq!(get_extension(Path::new("file")), None);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
//! GitHub utilities for SWE-agent
|
|
2
|
+
|
|
3
|
+
use crate::exceptions::{Result, SWEAgentError};
|
|
4
|
+
use regex::Regex;
|
|
5
|
+
use serde::{Deserialize, Serialize};
|
|
6
|
+
|
|
7
|
+
/// Parsed GitHub repository information
|
|
8
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
9
|
+
pub struct GithubRepoInfo {
|
|
10
|
+
pub owner: String,
|
|
11
|
+
pub repo: String,
|
|
12
|
+
pub full_name: String,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// Parsed GitHub issue information
|
|
16
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
17
|
+
pub struct GithubIssueInfo {
|
|
18
|
+
pub owner: String,
|
|
19
|
+
pub repo: String,
|
|
20
|
+
pub issue_number: u64,
|
|
21
|
+
pub full_name: String,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// Check if a string is a GitHub repository URL
|
|
25
|
+
pub fn is_github_repo_url(url: &str) -> bool {
|
|
26
|
+
let patterns = [
|
|
27
|
+
r"^https?://github\.com/[\w.-]+/[\w.-]+/?$",
|
|
28
|
+
r"^https?://github\.com/[\w.-]+/[\w.-]+\.git$",
|
|
29
|
+
r"^git@github\.com:[\w.-]+/[\w.-]+\.git$",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
patterns
|
|
33
|
+
.iter()
|
|
34
|
+
.any(|p| Regex::new(p).map(|re| re.is_match(url)).unwrap_or(false))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Check if a string is a GitHub issue URL
|
|
38
|
+
pub fn is_github_issue_url(url: &str) -> bool {
|
|
39
|
+
let pattern = r"^https?://github\.com/[\w.-]+/[\w.-]+/issues/\d+$";
|
|
40
|
+
Regex::new(pattern)
|
|
41
|
+
.map(|re| re.is_match(url))
|
|
42
|
+
.unwrap_or(false)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Parse a GitHub repository URL
|
|
46
|
+
pub fn parse_github_repo_url(url: &str) -> Result<GithubRepoInfo> {
|
|
47
|
+
// Handle HTTPS URLs
|
|
48
|
+
let https_pattern = Regex::new(r"^https?://github\.com/([\w.-]+)/([\w.-]+?)(?:\.git)?/?$")
|
|
49
|
+
.map_err(|e| SWEAgentError::Unknown(e.to_string()))?;
|
|
50
|
+
|
|
51
|
+
if let Some(caps) = https_pattern.captures(url) {
|
|
52
|
+
let owner = caps.get(1).unwrap().as_str().to_string();
|
|
53
|
+
let repo = caps.get(2).unwrap().as_str().to_string();
|
|
54
|
+
return Ok(GithubRepoInfo {
|
|
55
|
+
full_name: format!("{}/{}", owner, repo),
|
|
56
|
+
owner,
|
|
57
|
+
repo,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Handle SSH URLs
|
|
62
|
+
let ssh_pattern = Regex::new(r"^git@github\.com:([\w.-]+)/([\w.-]+?)(?:\.git)?$")
|
|
63
|
+
.map_err(|e| SWEAgentError::Unknown(e.to_string()))?;
|
|
64
|
+
|
|
65
|
+
if let Some(caps) = ssh_pattern.captures(url) {
|
|
66
|
+
let owner = caps.get(1).unwrap().as_str().to_string();
|
|
67
|
+
let repo = caps.get(2).unwrap().as_str().to_string();
|
|
68
|
+
return Ok(GithubRepoInfo {
|
|
69
|
+
full_name: format!("{}/{}", owner, repo),
|
|
70
|
+
owner,
|
|
71
|
+
repo,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
Err(SWEAgentError::InvalidGithubUrl(format!(
|
|
76
|
+
"Could not parse GitHub URL: {}",
|
|
77
|
+
url
|
|
78
|
+
)))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Parse a GitHub issue URL
|
|
82
|
+
pub fn parse_github_issue_url(url: &str) -> Result<GithubIssueInfo> {
|
|
83
|
+
let pattern = Regex::new(r"^https?://github\.com/([\w.-]+)/([\w.-]+)/issues/(\d+)$")
|
|
84
|
+
.map_err(|e| SWEAgentError::Unknown(e.to_string()))?;
|
|
85
|
+
|
|
86
|
+
if let Some(caps) = pattern.captures(url) {
|
|
87
|
+
let owner = caps.get(1).unwrap().as_str().to_string();
|
|
88
|
+
let repo = caps.get(2).unwrap().as_str().to_string();
|
|
89
|
+
let issue_number: u64 = caps
|
|
90
|
+
.get(3)
|
|
91
|
+
.unwrap()
|
|
92
|
+
.as_str()
|
|
93
|
+
.parse()
|
|
94
|
+
.map_err(|_| SWEAgentError::InvalidGithubUrl("Invalid issue number".to_string()))?;
|
|
95
|
+
|
|
96
|
+
return Ok(GithubIssueInfo {
|
|
97
|
+
full_name: format!("{}/{}", owner, repo),
|
|
98
|
+
owner,
|
|
99
|
+
repo,
|
|
100
|
+
issue_number,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
Err(SWEAgentError::InvalidGithubUrl(format!(
|
|
105
|
+
"Could not parse GitHub issue URL: {}",
|
|
106
|
+
url
|
|
107
|
+
)))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// Build a GitHub repository URL from owner and repo
|
|
111
|
+
pub fn build_github_repo_url(owner: &str, repo: &str) -> String {
|
|
112
|
+
format!("https://github.com/{}/{}", owner, repo)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Build a GitHub issue URL
|
|
116
|
+
pub fn build_github_issue_url(owner: &str, repo: &str, issue_number: u64) -> String {
|
|
117
|
+
format!(
|
|
118
|
+
"https://github.com/{}/{}/issues/{}",
|
|
119
|
+
owner, repo, issue_number
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Build a GitHub raw content URL
|
|
124
|
+
pub fn build_github_raw_url(owner: &str, repo: &str, branch: &str, path: &str) -> String {
|
|
125
|
+
format!(
|
|
126
|
+
"https://raw.githubusercontent.com/{}/{}/{}/{}",
|
|
127
|
+
owner, repo, branch, path
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#[cfg(test)]
|
|
132
|
+
mod tests {
|
|
133
|
+
use super::*;
|
|
134
|
+
|
|
135
|
+
#[test]
|
|
136
|
+
fn test_is_github_repo_url() {
|
|
137
|
+
assert!(is_github_repo_url("https://github.com/owner/repo"));
|
|
138
|
+
assert!(is_github_repo_url("https://github.com/owner/repo/"));
|
|
139
|
+
assert!(is_github_repo_url("https://github.com/owner/repo.git"));
|
|
140
|
+
assert!(is_github_repo_url("git@github.com:owner/repo.git"));
|
|
141
|
+
assert!(!is_github_repo_url("https://gitlab.com/owner/repo"));
|
|
142
|
+
assert!(!is_github_repo_url("not a url"));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#[test]
|
|
146
|
+
fn test_is_github_issue_url() {
|
|
147
|
+
assert!(is_github_issue_url(
|
|
148
|
+
"https://github.com/owner/repo/issues/123"
|
|
149
|
+
));
|
|
150
|
+
assert!(!is_github_issue_url("https://github.com/owner/repo"));
|
|
151
|
+
assert!(!is_github_issue_url(
|
|
152
|
+
"https://github.com/owner/repo/pull/123"
|
|
153
|
+
));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#[test]
|
|
157
|
+
fn test_parse_github_repo_url() {
|
|
158
|
+
let info = parse_github_repo_url("https://github.com/elizaos/eliza").unwrap();
|
|
159
|
+
assert_eq!(info.owner, "elizaos");
|
|
160
|
+
assert_eq!(info.repo, "eliza");
|
|
161
|
+
assert_eq!(info.full_name, "elizaos/eliza");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#[test]
|
|
165
|
+
fn test_parse_github_issue_url() {
|
|
166
|
+
let info = parse_github_issue_url("https://github.com/elizaos/eliza/issues/42").unwrap();
|
|
167
|
+
assert_eq!(info.owner, "elizaos");
|
|
168
|
+
assert_eq!(info.repo, "eliza");
|
|
169
|
+
assert_eq!(info.issue_number, 42);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
//! Logging utilities for SWE-agent
|
|
2
|
+
|
|
3
|
+
use tracing::{debug, error, info, warn, Level};
|
|
4
|
+
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
|
5
|
+
|
|
6
|
+
/// Initialize the logging system
|
|
7
|
+
pub fn init_logging() {
|
|
8
|
+
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
|
9
|
+
|
|
10
|
+
tracing_subscriber::registry()
|
|
11
|
+
.with(fmt::layer())
|
|
12
|
+
.with(filter)
|
|
13
|
+
.init();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// Initialize logging with a specific level
|
|
17
|
+
pub fn init_logging_with_level(level: Level) {
|
|
18
|
+
let filter = EnvFilter::new(level.as_str());
|
|
19
|
+
|
|
20
|
+
tracing_subscriber::registry()
|
|
21
|
+
.with(fmt::layer())
|
|
22
|
+
.with(filter)
|
|
23
|
+
.init();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Log an info message with agent context
|
|
27
|
+
pub fn log_agent_info(agent_name: &str, message: &str) {
|
|
28
|
+
info!(agent = agent_name, "{}", message);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Log a debug message with agent context
|
|
32
|
+
pub fn log_agent_debug(agent_name: &str, message: &str) {
|
|
33
|
+
debug!(agent = agent_name, "{}", message);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Log a warning with agent context
|
|
37
|
+
pub fn log_agent_warn(agent_name: &str, message: &str) {
|
|
38
|
+
warn!(agent = agent_name, "{}", message);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Log an error with agent context
|
|
42
|
+
pub fn log_agent_error(agent_name: &str, message: &str) {
|
|
43
|
+
error!(agent = agent_name, "{}", message);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Log step information
|
|
47
|
+
pub fn log_step(step_num: usize, thought: &str, action: &str) {
|
|
48
|
+
info!(
|
|
49
|
+
step = step_num,
|
|
50
|
+
"💭 THOUGHT\n{}\n\n🎬 ACTION\n{}", thought, action
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Log observation
|
|
55
|
+
pub fn log_observation(observation: &str) {
|
|
56
|
+
debug!(
|
|
57
|
+
len = observation.len(),
|
|
58
|
+
"📋 OBSERVATION: {}",
|
|
59
|
+
if observation.len() > 200 {
|
|
60
|
+
format!("{}...", &observation[..200])
|
|
61
|
+
} else {
|
|
62
|
+
observation.to_string()
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//! Utility functions and helpers for SWE-agent
|
|
2
|
+
//!
|
|
3
|
+
//! This module contains common utilities used throughout the implementation.
|
|
4
|
+
|
|
5
|
+
pub mod config;
|
|
6
|
+
pub mod files;
|
|
7
|
+
pub mod github;
|
|
8
|
+
pub mod log;
|
|
9
|
+
pub mod serialization;
|
|
10
|
+
pub mod template;
|
|
11
|
+
|
|
12
|
+
pub use config::*;
|
|
13
|
+
pub use files::*;
|
|
14
|
+
pub use github::*;
|
|
15
|
+
pub use log::*;
|
|
16
|
+
pub use serialization::*;
|
|
17
|
+
pub use template::*;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
//! Serialization utilities for SWE-agent
|
|
2
|
+
|
|
3
|
+
use crate::exceptions::{Result, SWEAgentError};
|
|
4
|
+
use serde::{Deserialize, Serialize};
|
|
5
|
+
use std::collections::HashMap;
|
|
6
|
+
|
|
7
|
+
/// Convert a value to YAML string with proper line breaks
|
|
8
|
+
pub fn to_yaml_with_linebreaks<T: Serialize>(value: &T) -> Result<String> {
|
|
9
|
+
serde_yaml::to_string(value).map_err(|e| SWEAgentError::SerializationError(e.to_string()))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/// Convert to YAML literal string (preserving multiline formatting)
|
|
13
|
+
pub fn convert_to_yaml_literal_string(s: &str) -> String {
|
|
14
|
+
if s.contains('\n') {
|
|
15
|
+
format!("|\n{}", indent_string(s, 2))
|
|
16
|
+
} else {
|
|
17
|
+
serde_yaml::to_string(&s).unwrap_or_else(|_| format!("\"{}\"", s.replace('"', "\\\"")))
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Indent a string by a given number of spaces
|
|
22
|
+
fn indent_string(s: &str, spaces: usize) -> String {
|
|
23
|
+
let indent = " ".repeat(spaces);
|
|
24
|
+
s.lines()
|
|
25
|
+
.map(|line| format!("{}{}", indent, line))
|
|
26
|
+
.collect::<Vec<_>>()
|
|
27
|
+
.join("\n")
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// Merge two nested dictionaries
|
|
31
|
+
pub fn merge_nested_dicts(
|
|
32
|
+
base: &HashMap<String, serde_json::Value>,
|
|
33
|
+
overlay: &HashMap<String, serde_json::Value>,
|
|
34
|
+
) -> HashMap<String, serde_json::Value> {
|
|
35
|
+
let mut result = base.clone();
|
|
36
|
+
|
|
37
|
+
for (key, value) in overlay {
|
|
38
|
+
let should_merge = result.get(key).map(|base_value| {
|
|
39
|
+
matches!(
|
|
40
|
+
(base_value, value),
|
|
41
|
+
(serde_json::Value::Object(_), serde_json::Value::Object(_))
|
|
42
|
+
)
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
match should_merge {
|
|
46
|
+
Some(true) => {
|
|
47
|
+
if let (
|
|
48
|
+
Some(serde_json::Value::Object(base_obj)),
|
|
49
|
+
serde_json::Value::Object(overlay_obj),
|
|
50
|
+
) = (result.get(key), value)
|
|
51
|
+
{
|
|
52
|
+
let base_map: HashMap<String, serde_json::Value> = base_obj
|
|
53
|
+
.iter()
|
|
54
|
+
.map(|(k, v)| (k.clone(), v.clone()))
|
|
55
|
+
.collect();
|
|
56
|
+
let overlay_map: HashMap<String, serde_json::Value> = overlay_obj
|
|
57
|
+
.iter()
|
|
58
|
+
.map(|(k, v)| (k.clone(), v.clone()))
|
|
59
|
+
.collect();
|
|
60
|
+
let merged = merge_nested_dicts(&base_map, &overlay_map);
|
|
61
|
+
result.insert(
|
|
62
|
+
key.clone(),
|
|
63
|
+
serde_json::Value::Object(merged.into_iter().collect()),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
Some(false) => {
|
|
68
|
+
result.insert(key.clone(), value.clone());
|
|
69
|
+
}
|
|
70
|
+
None => {
|
|
71
|
+
result.insert(key.clone(), value.clone());
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
result
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Parse arguments into a nested dictionary
|
|
80
|
+
pub fn parse_args_to_nested_dict(args: &[String]) -> HashMap<String, serde_json::Value> {
|
|
81
|
+
let mut result = HashMap::new();
|
|
82
|
+
|
|
83
|
+
for arg in args {
|
|
84
|
+
if let Some(eq_pos) = arg.find('=') {
|
|
85
|
+
let key = &arg[..eq_pos];
|
|
86
|
+
let value = &arg[eq_pos + 1..];
|
|
87
|
+
|
|
88
|
+
// Parse the key path (e.g., "agent.model.name")
|
|
89
|
+
let parts: Vec<&str> = key.split('.').collect();
|
|
90
|
+
set_nested_value(&mut result, &parts, value);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
result
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Set a value in a nested dictionary
|
|
98
|
+
fn set_nested_value(dict: &mut HashMap<String, serde_json::Value>, path: &[&str], value: &str) {
|
|
99
|
+
if path.is_empty() {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if path.len() == 1 {
|
|
104
|
+
// Try to parse as JSON, otherwise use as string
|
|
105
|
+
let json_value = serde_json::from_str(value)
|
|
106
|
+
.unwrap_or_else(|_| serde_json::Value::String(value.to_string()));
|
|
107
|
+
dict.insert(path[0].to_string(), json_value);
|
|
108
|
+
} else {
|
|
109
|
+
let entry = dict
|
|
110
|
+
.entry(path[0].to_string())
|
|
111
|
+
.or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
|
|
112
|
+
|
|
113
|
+
if let serde_json::Value::Object(obj) = entry {
|
|
114
|
+
let mut inner: HashMap<String, serde_json::Value> =
|
|
115
|
+
obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
|
|
116
|
+
set_nested_value(&mut inner, &path[1..], value);
|
|
117
|
+
*obj = inner.into_iter().collect();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Shorten a string to a maximum length
|
|
123
|
+
pub fn shorten_string(s: &str, max_len: usize) -> String {
|
|
124
|
+
if s.len() <= max_len {
|
|
125
|
+
s.to_string()
|
|
126
|
+
} else {
|
|
127
|
+
format!("{}...", &s[..max_len.saturating_sub(3)])
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Shorten all strings in a dictionary
|
|
132
|
+
pub fn shorten_strings(dict: &HashMap<String, String>, max_len: usize) -> HashMap<String, String> {
|
|
133
|
+
dict.iter()
|
|
134
|
+
.map(|(k, v)| (k.clone(), shorten_string(v, max_len)))
|
|
135
|
+
.collect()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// Load a trajectory from a file
|
|
139
|
+
pub fn load_trajectory<T: for<'de> Deserialize<'de>>(path: &std::path::Path) -> Result<T> {
|
|
140
|
+
let contents = std::fs::read_to_string(path)?;
|
|
141
|
+
serde_json::from_str(&contents).map_err(|e| SWEAgentError::SerializationError(e.to_string()))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// Save a trajectory to a file
|
|
145
|
+
pub fn save_trajectory<T: Serialize>(path: &std::path::Path, data: &T) -> Result<()> {
|
|
146
|
+
let contents = serde_json::to_string_pretty(data)?;
|
|
147
|
+
std::fs::write(path, contents)?;
|
|
148
|
+
Ok(())
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#[cfg(test)]
|
|
152
|
+
mod tests {
|
|
153
|
+
use super::*;
|
|
154
|
+
|
|
155
|
+
#[test]
|
|
156
|
+
fn test_shorten_string() {
|
|
157
|
+
assert_eq!(shorten_string("hello", 10), "hello");
|
|
158
|
+
assert_eq!(shorten_string("hello world", 8), "hello...");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#[test]
|
|
162
|
+
fn test_convert_to_yaml_literal_string() {
|
|
163
|
+
let single = convert_to_yaml_literal_string("hello");
|
|
164
|
+
assert!(!single.starts_with('|'));
|
|
165
|
+
|
|
166
|
+
let multi = convert_to_yaml_literal_string("hello\nworld");
|
|
167
|
+
assert!(multi.starts_with('|'));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#[test]
|
|
171
|
+
fn test_parse_args_to_nested_dict() {
|
|
172
|
+
let args = vec![
|
|
173
|
+
"agent.model.name=gpt-4".to_string(),
|
|
174
|
+
"env.timeout=30".to_string(),
|
|
175
|
+
];
|
|
176
|
+
let dict = parse_args_to_nested_dict(&args);
|
|
177
|
+
|
|
178
|
+
assert!(dict.contains_key("agent"));
|
|
179
|
+
assert!(dict.contains_key("env"));
|
|
180
|
+
}
|
|
181
|
+
}
|