@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.
Files changed (323) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +270 -0
  3. package/package.json +71 -0
  4. package/python/LICENSE +21 -0
  5. package/python/config/README.md +15 -0
  6. package/python/config/bash_only.yaml +222 -0
  7. package/python/config/benchmarks/250212_sweagent_heavy_sbl.yaml +188 -0
  8. package/python/config/benchmarks/250225_anthropic_filemap_simple_review.yaml +75 -0
  9. package/python/config/benchmarks/250522_anthropic_filemap_simple_review.yaml +92 -0
  10. package/python/config/benchmarks/250526_anthropic_filemap_simple_review_sbl.yaml +93 -0
  11. package/python/config/benchmarks/anthropic_filemap_multilingual.yaml +66 -0
  12. package/python/config/coding_challenge.yaml +104 -0
  13. package/python/config/default.yaml +69 -0
  14. package/python/config/default_backticks.yaml +69 -0
  15. package/python/config/default_mm_no_images.yaml +82 -0
  16. package/python/config/default_mm_with_images.yaml +83 -0
  17. package/python/config/demo/default.yaml +80 -0
  18. package/python/config/demo/no_instructions.yaml +69 -0
  19. package/python/config/demo/only_bash.yaml +60 -0
  20. package/python/config/exotic/default_shell.yaml +52 -0
  21. package/python/config/exotic/windowed_replace.yaml +125 -0
  22. package/python/config/exotic/windowed_replace_late_repro.yaml +127 -0
  23. package/python/config/human/human.yaml +24 -0
  24. package/python/config/human/human_demo.yaml +52 -0
  25. package/python/config/sweagent_0_7/07.yaml +101 -0
  26. package/python/config/sweagent_0_7/07_fcalling.yaml +100 -0
  27. package/python/config/sweagent_0_7/07_from_url.yaml +114 -0
  28. package/python/config/sweagent_0_7/07_thought_action.yaml +102 -0
  29. package/python/config/sweagent_0_7/07_thought_action_xml.yaml +96 -0
  30. package/python/mlc_config.json +44 -0
  31. package/python/pyproject.toml +262 -0
  32. package/python/sweagent/__init__.py +114 -0
  33. package/python/sweagent/__main__.py +4 -0
  34. package/python/sweagent/agent/__init__.py +0 -0
  35. package/python/sweagent/agent/action_sampler.py +317 -0
  36. package/python/sweagent/agent/agents.py +1294 -0
  37. package/python/sweagent/agent/extra/shell_agent.py +106 -0
  38. package/python/sweagent/agent/history_processors.py +399 -0
  39. package/python/sweagent/agent/hooks/__init__.py +0 -0
  40. package/python/sweagent/agent/hooks/abstract.py +139 -0
  41. package/python/sweagent/agent/hooks/status.py +34 -0
  42. package/python/sweagent/agent/models.py +896 -0
  43. package/python/sweagent/agent/problem_statement.py +312 -0
  44. package/python/sweagent/agent/reviewer.py +664 -0
  45. package/python/sweagent/environment/__init__.py +0 -0
  46. package/python/sweagent/environment/hooks/__init__.py +0 -0
  47. package/python/sweagent/environment/hooks/abstract.py +60 -0
  48. package/python/sweagent/environment/hooks/status.py +28 -0
  49. package/python/sweagent/environment/repo.py +219 -0
  50. package/python/sweagent/environment/swe_env.py +276 -0
  51. package/python/sweagent/exceptions.py +54 -0
  52. package/python/sweagent/inspector/README.md +6 -0
  53. package/python/sweagent/inspector/__init__.py +0 -0
  54. package/python/sweagent/inspector/favicon.ico +0 -0
  55. package/python/sweagent/inspector/fileViewer.js +354 -0
  56. package/python/sweagent/inspector/icons/computer.png +0 -0
  57. package/python/sweagent/inspector/icons/edit_icon.svg +11 -0
  58. package/python/sweagent/inspector/icons/swe-agent-logo-50.png +0 -0
  59. package/python/sweagent/inspector/icons/swellama_blue.png +0 -0
  60. package/python/sweagent/inspector/icons/swellama_brown.png +0 -0
  61. package/python/sweagent/inspector/icons/swellama_grey.png +0 -0
  62. package/python/sweagent/inspector/icons/swellama_tan.png +0 -0
  63. package/python/sweagent/inspector/index.html +25 -0
  64. package/python/sweagent/inspector/server.py +354 -0
  65. package/python/sweagent/inspector/static.py +169 -0
  66. package/python/sweagent/inspector/style.css +454 -0
  67. package/python/sweagent/run/__init__.py +0 -0
  68. package/python/sweagent/run/_progress.py +158 -0
  69. package/python/sweagent/run/batch_instances.py +419 -0
  70. package/python/sweagent/run/common.py +387 -0
  71. package/python/sweagent/run/compare_runs.py +123 -0
  72. package/python/sweagent/run/extract_pred.py +19 -0
  73. package/python/sweagent/run/hooks/__init__.py +0 -0
  74. package/python/sweagent/run/hooks/abstract.py +67 -0
  75. package/python/sweagent/run/hooks/apply_patch.py +106 -0
  76. package/python/sweagent/run/hooks/open_pr.py +244 -0
  77. package/python/sweagent/run/hooks/swe_bench_evaluate.py +113 -0
  78. package/python/sweagent/run/inspector_cli.py +493 -0
  79. package/python/sweagent/run/merge_predictions.py +64 -0
  80. package/python/sweagent/run/quick_stats.py +96 -0
  81. package/python/sweagent/run/remove_unfinished.py +63 -0
  82. package/python/sweagent/run/rich_test.py +91 -0
  83. package/python/sweagent/run/run.py +147 -0
  84. package/python/sweagent/run/run_batch.py +442 -0
  85. package/python/sweagent/run/run_replay.py +219 -0
  86. package/python/sweagent/run/run_shell.py +155 -0
  87. package/python/sweagent/run/run_single.py +225 -0
  88. package/python/sweagent/run/run_traj_to_demo.py +85 -0
  89. package/python/sweagent/tools/__init__.py +0 -0
  90. package/python/sweagent/tools/bundle.py +57 -0
  91. package/python/sweagent/tools/commands.py +220 -0
  92. package/python/sweagent/tools/parsing.py +619 -0
  93. package/python/sweagent/tools/tools.py +430 -0
  94. package/python/sweagent/tools/utils.py +108 -0
  95. package/python/sweagent/types.py +102 -0
  96. package/python/sweagent/utils/__init__.py +0 -0
  97. package/python/sweagent/utils/config.py +80 -0
  98. package/python/sweagent/utils/files.py +27 -0
  99. package/python/sweagent/utils/github.py +118 -0
  100. package/python/sweagent/utils/jinja_warnings.py +14 -0
  101. package/python/sweagent/utils/log.py +175 -0
  102. package/python/sweagent/utils/patch_formatter.py +152 -0
  103. package/python/sweagent/utils/serialization.py +45 -0
  104. package/python/tests/__init__.py +0 -0
  105. package/python/tests/conftest.py +191 -0
  106. package/python/tests/test_agent.py +258 -0
  107. package/python/tests/test_batch_instance.py +43 -0
  108. package/python/tests/test_commands/_interactive_dummy.py +35 -0
  109. package/python/tests/test_commands/interactive_dummy_wrapper.sh +29 -0
  110. package/python/tests/test_data/config_files/dummy_interactive.yaml +62 -0
  111. package/python/tests/test_data/data_sources/ctf/crypto/Katy/Dockerfile +20 -0
  112. package/python/tests/test_data/data_sources/ctf/crypto/Katy/README.md +13 -0
  113. package/python/tests/test_data/data_sources/ctf/crypto/Katy/challenge.json +12 -0
  114. package/python/tests/test_data/data_sources/ctf/crypto/Katy/customrandom.c +50 -0
  115. package/python/tests/test_data/data_sources/ctf/crypto/Katy/docker-compose.yml +14 -0
  116. package/python/tests/test_data/data_sources/ctf/crypto/Katy/release +0 -0
  117. package/python/tests/test_data/data_sources/ctf/crypto/Katy/server +0 -0
  118. package/python/tests/test_data/data_sources/ctf/crypto/Katy/solver.py +12 -0
  119. package/python/tests/test_data/data_sources/ctf/forensics/flash/README.md +16 -0
  120. package/python/tests/test_data/data_sources/ctf/forensics/flash/challenge.json +9 -0
  121. package/python/tests/test_data/data_sources/ctf/forensics/flash/flash_c8429a430278283c0e571baebca3d139.zip +0 -0
  122. package/python/tests/test_data/data_sources/ctf/misc/networking_1/README.md +15 -0
  123. package/python/tests/test_data/data_sources/ctf/misc/networking_1/challenge.json +10 -0
  124. package/python/tests/test_data/data_sources/ctf/misc/networking_1/networking.pcap +0 -0
  125. package/python/tests/test_data/data_sources/ctf/pwn/warmup/Dockerfile +28 -0
  126. package/python/tests/test_data/data_sources/ctf/pwn/warmup/README.md +14 -0
  127. package/python/tests/test_data/data_sources/ctf/pwn/warmup/challenge.json +14 -0
  128. package/python/tests/test_data/data_sources/ctf/pwn/warmup/docker-compose.yml +14 -0
  129. package/python/tests/test_data/data_sources/ctf/pwn/warmup/flag.txt +1 -0
  130. package/python/tests/test_data/data_sources/ctf/pwn/warmup/warmup +0 -0
  131. package/python/tests/test_data/data_sources/ctf/pwn/warmup/warmup.c +26 -0
  132. package/python/tests/test_data/data_sources/ctf/pwn/warmup/warmup.py +9 -0
  133. package/python/tests/test_data/data_sources/ctf/rev/rock/README.md +14 -0
  134. package/python/tests/test_data/data_sources/ctf/rev/rock/challenge.json +8 -0
  135. package/python/tests/test_data/data_sources/ctf/rev/rock/rock +0 -0
  136. package/python/tests/test_data/data_sources/ctf/rev/rock/rock.cpp +167 -0
  137. package/python/tests/test_data/data_sources/ctf/rev/rock/solution.cpp +24 -0
  138. package/python/tests/test_data/data_sources/ctf/rev/rock/test_solver/solution.py +6 -0
  139. package/python/tests/test_data/data_sources/ctf/rev/rock/test_solver/test.sh +10 -0
  140. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/000-default.conf +18 -0
  141. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/Dockerfile +20 -0
  142. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/cgi/file.pl +38 -0
  143. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/cgi/forms.pl +40 -0
  144. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/cgi/hello.pl +11 -0
  145. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/challenge.json +12 -0
  146. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/docker-compose.yml +14 -0
  147. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/flag +1 -0
  148. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/index.html +11 -0
  149. package/python/tests/test_data/data_sources/ctf/web/i_got_id_demo/solution.txt +1 -0
  150. package/python/tests/test_data/data_sources/debug_20240322.json +1 -0
  151. package/python/tests/test_data/data_sources/expert_instances.yaml +16 -0
  152. package/python/tests/test_data/data_sources/human_eval.json +1 -0
  153. package/python/tests/test_data/data_sources/simple_instances.yaml +3 -0
  154. package/python/tests/test_data/data_sources/simple_instances_long.yaml +30 -0
  155. package/python/tests/test_data/data_sources/swe-bench-dev-easy.json +1 -0
  156. package/python/tests/test_data/data_sources/swe-bench-dev-easy_first_only.json +1 -0
  157. package/python/tests/test_data/data_sources/swe-bench-lite-test.json +1 -0
  158. 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
  159. 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
  160. 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
  161. 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
  162. 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
  163. 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
  164. 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
  165. 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
  166. package/python/tests/test_env.py +66 -0
  167. package/python/tests/test_env_utils.py +129 -0
  168. package/python/tests/test_history_processors.py +40 -0
  169. package/python/tests/test_models.py +23 -0
  170. package/python/tests/test_openai_live.py +164 -0
  171. package/python/tests/test_packaging.py +7 -0
  172. package/python/tests/test_parsing.py +131 -0
  173. package/python/tests/test_problem_statement_multimodal.py +111 -0
  174. package/python/tests/test_quick_stats.py +42 -0
  175. package/python/tests/test_run.py +37 -0
  176. package/python/tests/test_run_batch.py +110 -0
  177. package/python/tests/test_run_hooks.py +114 -0
  178. package/python/tests/test_run_replay.py +33 -0
  179. package/python/tests/test_run_single.py +125 -0
  180. package/python/tests/test_tools_command_parsing.py +193 -0
  181. package/python/tests/test_utils.py +15 -0
  182. package/python/tests/tools/__init__.py +0 -0
  183. package/python/tests/tools/conftest.py +12 -0
  184. package/python/tests/tools/test_default_utils.py +153 -0
  185. package/python/tests/tools/test_edit_replace.py +0 -0
  186. package/python/tests/tools/test_split_string.py +82 -0
  187. package/python/tests/utils.py +29 -0
  188. package/python/tools/diff_state/bin/_state_diff_state +52 -0
  189. package/python/tools/diff_state/config.yaml +2 -0
  190. package/python/tools/edit_anthropic/bin/_state_anthropic +21 -0
  191. package/python/tools/edit_anthropic/bin/str_replace_editor +710 -0
  192. package/python/tools/edit_anthropic/config.yaml +56 -0
  193. package/python/tools/edit_anthropic/install.sh +3 -0
  194. package/python/tools/filemap/bin/filemap +45 -0
  195. package/python/tools/filemap/config.yaml +9 -0
  196. package/python/tools/filemap/install.sh +2 -0
  197. package/python/tools/forfeit/bin/exit_forfeit +5 -0
  198. package/python/tools/forfeit/config.yaml +5 -0
  199. package/python/tools/image_tools/bin/view_image +36 -0
  200. package/python/tools/image_tools/config.yaml +9 -0
  201. package/python/tools/multilingual_setup/bin/do_nothing +2 -0
  202. package/python/tools/multilingual_setup/config.yaml +1 -0
  203. package/python/tools/multilingual_setup/install.sh +45 -0
  204. package/python/tools/registry/bin/_read_env +10 -0
  205. package/python/tools/registry/bin/_write_env +10 -0
  206. package/python/tools/registry/config.yaml +1 -0
  207. package/python/tools/registry/install.sh +6 -0
  208. package/python/tools/registry/lib/__init__.py +0 -0
  209. package/python/tools/registry/lib/registry.py +56 -0
  210. package/python/tools/review_on_submit_m/README.md +6 -0
  211. package/python/tools/review_on_submit_m/bin/submit +54 -0
  212. package/python/tools/review_on_submit_m/config.yaml +6 -0
  213. package/python/tools/review_on_submit_m/install.sh +0 -0
  214. package/python/tools/search/bin/find_file +31 -0
  215. package/python/tools/search/bin/search_dir +39 -0
  216. package/python/tools/search/bin/search_file +55 -0
  217. package/python/tools/search/config.yaml +37 -0
  218. package/python/tools/search/install.sh +3 -0
  219. package/python/tools/submit/bin/submit +17 -0
  220. package/python/tools/submit/config.yaml +5 -0
  221. package/python/tools/web_browser/bin/click_mouse +41 -0
  222. package/python/tools/web_browser/bin/close_site +28 -0
  223. package/python/tools/web_browser/bin/double_click_mouse +37 -0
  224. package/python/tools/web_browser/bin/drag_mouse +46 -0
  225. package/python/tools/web_browser/bin/execute_script_on_page +39 -0
  226. package/python/tools/web_browser/bin/get_console_output +48 -0
  227. package/python/tools/web_browser/bin/move_mouse +35 -0
  228. package/python/tools/web_browser/bin/navigate_back +33 -0
  229. package/python/tools/web_browser/bin/navigate_forward +33 -0
  230. package/python/tools/web_browser/bin/open_site +36 -0
  231. package/python/tools/web_browser/bin/press_keys_on_page +51 -0
  232. package/python/tools/web_browser/bin/reload_page +33 -0
  233. package/python/tools/web_browser/bin/run_web_browser_server +394 -0
  234. package/python/tools/web_browser/bin/screenshot_site +38 -0
  235. package/python/tools/web_browser/bin/scroll_on_page +40 -0
  236. package/python/tools/web_browser/bin/set_browser_window_size +40 -0
  237. package/python/tools/web_browser/bin/type_text +34 -0
  238. package/python/tools/web_browser/bin/wait_time +39 -0
  239. package/python/tools/web_browser/config.yaml +155 -0
  240. package/python/tools/web_browser/install.sh +22 -0
  241. package/python/tools/web_browser/lib/browser_manager.py +404 -0
  242. package/python/tools/web_browser/lib/web_browser_config.py +33 -0
  243. package/python/tools/web_browser/lib/web_browser_utils.py +126 -0
  244. package/python/tools/web_browser/test_console.html +1 -0
  245. package/python/tools/windowed/bin/_state +25 -0
  246. package/python/tools/windowed/bin/create +29 -0
  247. package/python/tools/windowed/bin/goto +37 -0
  248. package/python/tools/windowed/bin/open +49 -0
  249. package/python/tools/windowed/bin/scroll_down +12 -0
  250. package/python/tools/windowed/bin/scroll_up +13 -0
  251. package/python/tools/windowed/config.yaml +38 -0
  252. package/python/tools/windowed/install.sh +15 -0
  253. package/python/tools/windowed/lib/__init__.py +0 -0
  254. package/python/tools/windowed/lib/flake8_utils.py +147 -0
  255. package/python/tools/windowed/lib/windowed_file.py +312 -0
  256. package/python/tools/windowed_edit_linting/bin/edit +128 -0
  257. package/python/tools/windowed_edit_linting/config.yaml +31 -0
  258. package/python/tools/windowed_edit_linting/install.sh +5 -0
  259. package/python/tools/windowed_edit_replace/bin/edit +172 -0
  260. package/python/tools/windowed_edit_replace/bin/insert +77 -0
  261. package/python/tools/windowed_edit_replace/config.yaml +60 -0
  262. package/python/tools/windowed_edit_replace/install.sh +5 -0
  263. package/python/tools/windowed_edit_rewrite/bin/edit +78 -0
  264. package/python/tools/windowed_edit_rewrite/config.yaml +11 -0
  265. package/python/tools/windowed_edit_rewrite/install.sh +5 -0
  266. package/python/trajectories/demonstrations/ctf/crypto/BabyEncryption.traj +318 -0
  267. package/python/trajectories/demonstrations/ctf/crypto/BabyTimeCapsule.traj +197 -0
  268. package/python/trajectories/demonstrations/ctf/crypto/eps.traj +289 -0
  269. package/python/trajectories/demonstrations/ctf/crypto/katy.traj +368 -0
  270. package/python/trajectories/demonstrations/ctf/forensics/flash.traj +102 -0
  271. package/python/trajectories/demonstrations/ctf/misc/networking_1.traj +102 -0
  272. package/python/trajectories/demonstrations/ctf/pwn/warmup.traj +159 -0
  273. package/python/trajectories/demonstrations/ctf/rev/rock.traj +251 -0
  274. package/python/trajectories/demonstrations/ctf/web/i_got_id_demo.traj +422 -0
  275. package/python/trajectories/demonstrations/function_calling_simple.traj +151 -0
  276. 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
  277. 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
  278. 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
  279. 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
  280. package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__function_calling__install-1/marshmallow-code__marshmallow-1867.traj +594 -0
  281. package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__function_calling_replace__install-1/marshmallow-code__marshmallow-1867.traj +592 -0
  282. package/python/trajectories/demonstrations/replay__marshmallow-code__marshmallow-1867__function_calling_replace_from_source/marshmallow-code__marshmallow-1867.traj +3316 -0
  283. 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
  284. 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
  285. package/python/trajectories/demonstrations/str_replace_anthropic_demo.yaml +432 -0
  286. package/rust/Cargo.toml +100 -0
  287. package/rust/README.md +49 -0
  288. package/rust/src/agent/action_sampler.rs +130 -0
  289. package/rust/src/agent/agents.rs +1029 -0
  290. package/rust/src/agent/history_processors.rs +277 -0
  291. package/rust/src/agent/hooks/mod.rs +208 -0
  292. package/rust/src/agent/mod.rs +24 -0
  293. package/rust/src/agent/models.rs +837 -0
  294. package/rust/src/agent/problem_statement.rs +355 -0
  295. package/rust/src/agent/reviewer.rs +505 -0
  296. package/rust/src/bin/sweagent.rs +784 -0
  297. package/rust/src/environment/deployment.rs +631 -0
  298. package/rust/src/environment/hooks/mod.rs +114 -0
  299. package/rust/src/environment/mod.rs +16 -0
  300. package/rust/src/environment/repo.rs +265 -0
  301. package/rust/src/environment/runtime.rs +237 -0
  302. package/rust/src/environment/swe_env.rs +248 -0
  303. package/rust/src/exceptions.rs +228 -0
  304. package/rust/src/lib.rs +68 -0
  305. package/rust/src/monitoring.rs +482 -0
  306. package/rust/src/run/hooks/mod.rs +134 -0
  307. package/rust/src/run/mod.rs +12 -0
  308. package/rust/src/run/run_batch.rs +563 -0
  309. package/rust/src/run/run_single.rs +196 -0
  310. package/rust/src/tools/bundle.rs +224 -0
  311. package/rust/src/tools/commands.rs +173 -0
  312. package/rust/src/tools/mod.rs +295 -0
  313. package/rust/src/tools/parsing.rs +354 -0
  314. package/rust/src/tools/registry.rs +143 -0
  315. package/rust/src/types.rs +554 -0
  316. package/rust/src/utils/config.rs +105 -0
  317. package/rust/src/utils/files.rs +137 -0
  318. package/rust/src/utils/github.rs +171 -0
  319. package/rust/src/utils/log.rs +65 -0
  320. package/rust/src/utils/mod.rs +17 -0
  321. package/rust/src/utils/serialization.rs +181 -0
  322. package/rust/src/utils/template.rs +173 -0
  323. 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
+ }