alysis-code 0.13.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (458) hide show
  1. alysis_code/__init__.py +3 -0
  2. alysis_code/__main__.py +4 -0
  3. alysis_code/_build_info.py +14 -0
  4. alysis_code/account_login.py +468 -0
  5. alysis_code/agent/README.md +35 -0
  6. alysis_code/agent/__init__.py +11 -0
  7. alysis_code/agent/acceptance_contract.py +2217 -0
  8. alysis_code/agent/blast_radius.py +1403 -0
  9. alysis_code/agent/cache_keepalive.py +227 -0
  10. alysis_code/agent/completion_certificate.py +366 -0
  11. alysis_code/agent/completion_gate.py +306 -0
  12. alysis_code/agent/empty_response_stall.py +403 -0
  13. alysis_code/agent/errors.py +28 -0
  14. alysis_code/agent/llm_calls.py +475 -0
  15. alysis_code/agent/mutation_classification.py +227 -0
  16. alysis_code/agent/prompt_context.py +2508 -0
  17. alysis_code/agent/read_ledger.py +253 -0
  18. alysis_code/agent/regression_baseline.py +642 -0
  19. alysis_code/agent/reproduction_first.py +610 -0
  20. alysis_code/agent/sensitive_output.py +629 -0
  21. alysis_code/agent/session.py +3218 -0
  22. alysis_code/agent/steering.py +191 -0
  23. alysis_code/agent/subagent_execution.py +5177 -0
  24. alysis_code/agent/subagent_workspace.py +666 -0
  25. alysis_code/agent/tools_assembly.py +4728 -0
  26. alysis_code/agent/turn/__init__.py +101 -0
  27. alysis_code/agent/turn/core.py +8483 -0
  28. alysis_code/agent/turn/events.py +113 -0
  29. alysis_code/agent/turn/exploration.py +590 -0
  30. alysis_code/agent/turn/interventions.py +65 -0
  31. alysis_code/agent/turn/read_cache.py +420 -0
  32. alysis_code/agent/turn/snapshot.py +179 -0
  33. alysis_code/agent/turn_contract.py +661 -0
  34. alysis_code/agent/turn_path.py +129 -0
  35. alysis_code/agent/verification.py +2885 -0
  36. alysis_code/agent/verification_commands.py +512 -0
  37. alysis_code/agent/verification_evidence.py +738 -0
  38. alysis_code/agent_loop.py +693 -0
  39. alysis_code/agent_runtimes/__init__.py +51 -0
  40. alysis_code/agent_runtimes/base.py +114 -0
  41. alysis_code/agent_runtimes/builtins.py +129 -0
  42. alysis_code/agent_runtimes/codex_cli.py +664 -0
  43. alysis_code/agent_runtimes/host.py +263 -0
  44. alysis_code/agent_runtimes/registry.py +64 -0
  45. alysis_code/agent_runtimes/service.py +150 -0
  46. alysis_code/agentbox_client.py +416 -0
  47. alysis_code/agentbox_integration.py +310 -0
  48. alysis_code/alysis_cloud.py +152 -0
  49. alysis_code/approval_scope.py +276 -0
  50. alysis_code/assets/README.md +33 -0
  51. alysis_code/assets/__init__.py +126 -0
  52. alysis_code/assets/asset_read_core.py +281 -0
  53. alysis_code/assets/budget_allocator.py +456 -0
  54. alysis_code/assets/comprehender.py +759 -0
  55. alysis_code/assets/index.py +654 -0
  56. alysis_code/assets/ingestion.py +275 -0
  57. alysis_code/assets/legacy_migration.py +413 -0
  58. alysis_code/assets/models.py +263 -0
  59. alysis_code/assets/ocr.py +239 -0
  60. alysis_code/assets/owl/ascii/f-000.txt +13 -0
  61. alysis_code/assets/owl/ascii/f-001.txt +13 -0
  62. alysis_code/assets/owl/ascii/f-002.txt +13 -0
  63. alysis_code/assets/owl/ascii/f-003.txt +13 -0
  64. alysis_code/assets/owl/ascii/f-004.txt +13 -0
  65. alysis_code/assets/owl/ascii/f-005.txt +13 -0
  66. alysis_code/assets/owl/ascii/f-006.txt +13 -0
  67. alysis_code/assets/owl/ascii/f-007.txt +13 -0
  68. alysis_code/assets/owl/ascii/f-008.txt +13 -0
  69. alysis_code/assets/owl/ascii/f-009.txt +13 -0
  70. alysis_code/assets/owl/ascii/f-010.txt +13 -0
  71. alysis_code/assets/owl/ascii/f-011.txt +13 -0
  72. alysis_code/assets/owl/ascii/f-012.txt +13 -0
  73. alysis_code/assets/owl/ascii/f-013.txt +13 -0
  74. alysis_code/assets/owl/ascii/f-014.txt +13 -0
  75. alysis_code/assets/owl/ascii/f-015.txt +13 -0
  76. alysis_code/assets/owl/ascii/f-016.txt +13 -0
  77. alysis_code/assets/owl/ascii/f-017.txt +13 -0
  78. alysis_code/assets/owl/ascii/f-018.txt +13 -0
  79. alysis_code/assets/owl/ascii/f-019.txt +13 -0
  80. alysis_code/assets/owl/ascii/f-020.txt +13 -0
  81. alysis_code/assets/owl/index.html +98 -0
  82. alysis_code/assets/owl/show-owl.sh +761 -0
  83. alysis_code/assets/paths.py +49 -0
  84. alysis_code/assets/plan_binding.py +326 -0
  85. alysis_code/assets/planner_context.py +466 -0
  86. alysis_code/assets/planner_tools.py +184 -0
  87. alysis_code/assets/prompts.py +101 -0
  88. alysis_code/assets/replanner_context.py +239 -0
  89. alysis_code/assets/surface.py +521 -0
  90. alysis_code/assets/untrusted_content.py +48 -0
  91. alysis_code/assets/usage_logger.py +94 -0
  92. alysis_code/assets/worker_mirror.py +428 -0
  93. alysis_code/assets/worker_section.py +303 -0
  94. alysis_code/assets/worker_tools.py +468 -0
  95. alysis_code/atomic_io.py +83 -0
  96. alysis_code/auth_diagnostics.py +272 -0
  97. alysis_code/background_runner.py +366 -0
  98. alysis_code/branding.py +270 -0
  99. alysis_code/budget_policy.py +390 -0
  100. alysis_code/build_identity.py +465 -0
  101. alysis_code/builtin_hooks/__init__.py +7 -0
  102. alysis_code/builtin_hooks/notify_done_windows.py +65 -0
  103. alysis_code/bwrap_etc.py +76 -0
  104. alysis_code/cancellation.py +41 -0
  105. alysis_code/capabilities.py +137 -0
  106. alysis_code/chatgpt_codex_static_provider.py +133 -0
  107. alysis_code/cli.py +51 -0
  108. alysis_code/cli_impl/__init__.py +1 -0
  109. alysis_code/cli_impl/assets_cli.py +537 -0
  110. alysis_code/cli_impl/assets_modal.py +412 -0
  111. alysis_code/cli_impl/chat/__init__.py +156 -0
  112. alysis_code/cli_impl/chat/commands.py +2616 -0
  113. alysis_code/cli_impl/chat/loop.py +4508 -0
  114. alysis_code/cli_impl/chat/mid_turn_policy.py +125 -0
  115. alysis_code/cli_impl/chat/rendering.py +444 -0
  116. alysis_code/cli_impl/chat/state.py +124 -0
  117. alysis_code/cli_impl/chat_resume.py +830 -0
  118. alysis_code/cli_impl/chat_slash_completer.py +258 -0
  119. alysis_code/cli_impl/commands/__init__.py +11 -0
  120. alysis_code/cli_impl/commands/_shared.py +89 -0
  121. alysis_code/cli_impl/commands/auth.py +623 -0
  122. alysis_code/cli_impl/commands/chat_resume_helpers.py +1531 -0
  123. alysis_code/cli_impl/commands/chat_state.py +158 -0
  124. alysis_code/cli_impl/commands/chat_status.py +1248 -0
  125. alysis_code/cli_impl/commands/chat_terminal.py +942 -0
  126. alysis_code/cli_impl/commands/chat_tui_panels.py +1018 -0
  127. alysis_code/cli_impl/commands/cli_common.py +1223 -0
  128. alysis_code/cli_impl/commands/cli_surface.py +77 -0
  129. alysis_code/cli_impl/commands/config.py +131 -0
  130. alysis_code/cli_impl/commands/conventions.py +85 -0
  131. alysis_code/cli_impl/commands/execution_helpers.py +350 -0
  132. alysis_code/cli_impl/commands/extensions.py +401 -0
  133. alysis_code/cli_impl/commands/forge.py +1282 -0
  134. alysis_code/cli_impl/commands/forge_asset_view.py +121 -0
  135. alysis_code/cli_impl/commands/forge_helpers.py +1215 -0
  136. alysis_code/cli_impl/commands/hooks.py +737 -0
  137. alysis_code/cli_impl/commands/ide_bridge.py +31 -0
  138. alysis_code/cli_impl/commands/mcp.py +700 -0
  139. alysis_code/cli_impl/commands/profile.py +453 -0
  140. alysis_code/cli_impl/commands/prompt_helpers.py +307 -0
  141. alysis_code/cli_impl/commands/report.py +88 -0
  142. alysis_code/cli_impl/commands/root.py +1118 -0
  143. alysis_code/cli_impl/commands/sandbox.py +184 -0
  144. alysis_code/cli_impl/commands/server.py +54 -0
  145. alysis_code/cli_impl/commands/sessions.py +252 -0
  146. alysis_code/cli_impl/commands/skills.py +404 -0
  147. alysis_code/cli_impl/commands/startup.py +946 -0
  148. alysis_code/cli_impl/commands/tools.py +335 -0
  149. alysis_code/cli_impl/commands/update.py +364 -0
  150. alysis_code/cli_impl/commands/welcome.py +972 -0
  151. alysis_code/cli_impl/config_menu.py +3882 -0
  152. alysis_code/cli_impl/forge.py +4509 -0
  153. alysis_code/cli_impl/forge_recovery.py +485 -0
  154. alysis_code/cli_impl/setup_wizard.py +2409 -0
  155. alysis_code/cli_impl/tui/__init__.py +58 -0
  156. alysis_code/cli_impl/tui/app.py +4551 -0
  157. alysis_code/cli_impl/tui/config.py +32 -0
  158. alysis_code/cli_impl/tui/config_flow.py +2754 -0
  159. alysis_code/cli_impl/tui/config_overlay.py +566 -0
  160. alysis_code/cli_impl/tui/content.py +78 -0
  161. alysis_code/cli_impl/tui/footer.py +218 -0
  162. alysis_code/cli_impl/tui/forge_status.py +136 -0
  163. alysis_code/cli_impl/tui/markdown.py +244 -0
  164. alysis_code/cli_impl/tui/owl.py +109 -0
  165. alysis_code/cli_impl/tui/plan_meta.py +477 -0
  166. alysis_code/cli_impl/tui/setup_app.py +519 -0
  167. alysis_code/cli_impl/tui/setup_flow.py +1622 -0
  168. alysis_code/cli_impl/tui/state.py +101 -0
  169. alysis_code/cli_impl/tui/subagent_identity.py +66 -0
  170. alysis_code/cli_impl/tui/subagent_panel.py +186 -0
  171. alysis_code/cli_impl/tui/surface.py +796 -0
  172. alysis_code/cli_impl/tui/transcript.py +514 -0
  173. alysis_code/cli_impl/tui/update_prompt.py +79 -0
  174. alysis_code/cli_impl/tui/workspace_guard.py +384 -0
  175. alysis_code/clipboard.py +172 -0
  176. alysis_code/code_review.py +1211 -0
  177. alysis_code/compaction/__init__.py +28 -0
  178. alysis_code/compaction/conversation_compactor.py +2932 -0
  179. alysis_code/compaction/importance.py +177 -0
  180. alysis_code/compaction/settings.py +297 -0
  181. alysis_code/compaction/tool_output_offload.py +447 -0
  182. alysis_code/config.py +3509 -0
  183. alysis_code/conflict_auto_resolver.py +895 -0
  184. alysis_code/context/__init__.py +1 -0
  185. alysis_code/context/tool_schema_budgeter.py +220 -0
  186. alysis_code/crash_diagnostics.py +282 -0
  187. alysis_code/custom_tools/README.md +34 -0
  188. alysis_code/custom_tools/__init__.py +43 -0
  189. alysis_code/custom_tools/discovery.py +903 -0
  190. alysis_code/custom_tools/runtime.py +1516 -0
  191. alysis_code/custom_tools/session.py +227 -0
  192. alysis_code/custom_tools/trust.py +232 -0
  193. alysis_code/diff_paths.py +113 -0
  194. alysis_code/direction_change.py +293 -0
  195. alysis_code/dispatch_timing.py +306 -0
  196. alysis_code/durable_service_manager.py +1236 -0
  197. alysis_code/edit_discipline.py +659 -0
  198. alysis_code/error_text.py +73 -0
  199. alysis_code/execution_budget.py +411 -0
  200. alysis_code/execution_context.py +915 -0
  201. alysis_code/execution_deadline.py +1065 -0
  202. alysis_code/execution_shared.py +1904 -0
  203. alysis_code/extensions/README.md +30 -0
  204. alysis_code/extensions/__init__.py +93 -0
  205. alysis_code/extensions/activation.py +138 -0
  206. alysis_code/extensions/install.py +1436 -0
  207. alysis_code/extensions/manifest.py +487 -0
  208. alysis_code/extensions/models.py +74 -0
  209. alysis_code/extensions/paths.py +56 -0
  210. alysis_code/extensions/registry.json +4 -0
  211. alysis_code/extensions/registry.py +52 -0
  212. alysis_code/extensions/state.py +83 -0
  213. alysis_code/extensions/workspace_trust.py +101 -0
  214. alysis_code/failed_task_evidence.py +369 -0
  215. alysis_code/failure_category.py +315 -0
  216. alysis_code/feedback_report.py +1647 -0
  217. alysis_code/file_classification.py +485 -0
  218. alysis_code/forge.py +2064 -0
  219. alysis_code/forge_completion.py +362 -0
  220. alysis_code/forge_events.py +475 -0
  221. alysis_code/frontmatter_utils.py +95 -0
  222. alysis_code/git_evidence.py +1181 -0
  223. alysis_code/git_ops.py +560 -0
  224. alysis_code/git_safe.py +62 -0
  225. alysis_code/git_worktrees.py +190 -0
  226. alysis_code/hooks/README.md +33 -0
  227. alysis_code/hooks/__init__.py +67 -0
  228. alysis_code/hooks/audit.py +171 -0
  229. alysis_code/hooks/config.py +225 -0
  230. alysis_code/hooks/dispatcher.py +1110 -0
  231. alysis_code/hooks/models.py +447 -0
  232. alysis_code/hooks/trust.py +202 -0
  233. alysis_code/host_actions.py +543 -0
  234. alysis_code/host_browser.py +103 -0
  235. alysis_code/ide/__init__.py +5 -0
  236. alysis_code/ide/activity_events.py +399 -0
  237. alysis_code/ide/approvals.py +337 -0
  238. alysis_code/ide/artifacts.py +153 -0
  239. alysis_code/ide/browser_egress_proxy.py +1076 -0
  240. alysis_code/ide/cdp_websocket_transport.py +1192 -0
  241. alysis_code/ide/change_ledger.py +1721 -0
  242. alysis_code/ide/context_blocks.py +979 -0
  243. alysis_code/ide/event_stream.py +531 -0
  244. alysis_code/ide/forge_protocol.py +3112 -0
  245. alysis_code/ide/forge_request_ledger.py +737 -0
  246. alysis_code/ide/health.py +965 -0
  247. alysis_code/ide/managed_browser.py +2251 -0
  248. alysis_code/ide/management_protocol.py +3414 -0
  249. alysis_code/ide/mcp_oauth_coordinator.py +744 -0
  250. alysis_code/ide/mcp_oauth_lifecycle.py +1504 -0
  251. alysis_code/ide/prompt_queue.py +1070 -0
  252. alysis_code/ide/protocol.py +191 -0
  253. alysis_code/ide/resumable_swarm.py +1543 -0
  254. alysis_code/ide/session_search.py +295 -0
  255. alysis_code/ide/stdio_bridge.py +9935 -0
  256. alysis_code/ide/structured_state.py +1579 -0
  257. alysis_code/ide/swarm_protocol.py +816 -0
  258. alysis_code/integration_gate.py +506 -0
  259. alysis_code/interactive_input_guard.py +39 -0
  260. alysis_code/interactive_plan_mode.py +26 -0
  261. alysis_code/internal_artifacts.py +179 -0
  262. alysis_code/knowledge_base.py +1409 -0
  263. alysis_code/knowledge_capture.py +1190 -0
  264. alysis_code/knowledge_librarian.py +605 -0
  265. alysis_code/language_policy.py +34 -0
  266. alysis_code/litellm_static_provider.py +535 -0
  267. alysis_code/llm/__init__.py +1 -0
  268. alysis_code/llm/anthropic_messages.py +2288 -0
  269. alysis_code/llm/base.py +71 -0
  270. alysis_code/llm/cache_capabilities.py +985 -0
  271. alysis_code/llm/cache_control_blocks.py +244 -0
  272. alysis_code/llm/cache_policy.py +388 -0
  273. alysis_code/llm/factory.py +373 -0
  274. alysis_code/llm/gemini_generate_content.py +2652 -0
  275. alysis_code/llm/gemini_interactions.py +739 -0
  276. alysis_code/llm/metadata.py +450 -0
  277. alysis_code/llm/openai_compat.py +2947 -0
  278. alysis_code/llm/openai_responses.py +2604 -0
  279. alysis_code/llm/protocols.py +609 -0
  280. alysis_code/llm/provider_limits.py +525 -0
  281. alysis_code/llm/request_plan.py +389 -0
  282. alysis_code/llm/request_shape.py +238 -0
  283. alysis_code/llm/streaming.py +108 -0
  284. alysis_code/llm/temperature_compat.py +78 -0
  285. alysis_code/llm/types.py +195 -0
  286. alysis_code/llm/usage_normalization.py +222 -0
  287. alysis_code/llm_error_display.py +315 -0
  288. alysis_code/logging_redaction.py +326 -0
  289. alysis_code/managed_host_deadline.py +196 -0
  290. alysis_code/mcp/README.md +33 -0
  291. alysis_code/mcp/__init__.py +24 -0
  292. alysis_code/mcp/client.py +1137 -0
  293. alysis_code/mcp/config.py +597 -0
  294. alysis_code/mcp/errors.py +113 -0
  295. alysis_code/mcp/forge_scope.py +154 -0
  296. alysis_code/mcp/jsonrpc.py +214 -0
  297. alysis_code/mcp/manager.py +2308 -0
  298. alysis_code/mcp/models.py +666 -0
  299. alysis_code/mcp/oauth.py +972 -0
  300. alysis_code/mcp/oauth_runtime.py +310 -0
  301. alysis_code/mcp/oauth_store.py +276 -0
  302. alysis_code/mcp/prompts.py +329 -0
  303. alysis_code/mcp/resources.py +295 -0
  304. alysis_code/mcp/roots.py +106 -0
  305. alysis_code/mcp/server_requests.py +75 -0
  306. alysis_code/mcp/token_store.py +859 -0
  307. alysis_code/mcp/transport_http.py +1338 -0
  308. alysis_code/mcp/transport_stdio.py +1267 -0
  309. alysis_code/mcp/untrusted_content.py +119 -0
  310. alysis_code/merge_conflict_reviewer.py +729 -0
  311. alysis_code/model_catalog/__init__.py +1 -0
  312. alysis_code/model_catalog/chatgpt_codex_subscription_snapshot.json +186 -0
  313. alysis_code/model_catalog/litellm_model_prices_snapshot.json +44715 -0
  314. alysis_code/model_catalog/litellm_model_prices_snapshot.meta.json +17 -0
  315. alysis_code/model_metadata_policy.py +223 -0
  316. alysis_code/model_metadata_utils.py +103 -0
  317. alysis_code/model_registry.py +1420 -0
  318. alysis_code/model_router.py +147 -0
  319. alysis_code/permission_policy.py +1016 -0
  320. alysis_code/personas.py +451 -0
  321. alysis_code/pipeline_facts.py +233 -0
  322. alysis_code/plan_assistant.py +4763 -0
  323. alysis_code/plan_mode.py +393 -0
  324. alysis_code/plan_reconciliation.py +1228 -0
  325. alysis_code/plan_repair.py +652 -0
  326. alysis_code/plan_validation.py +1099 -0
  327. alysis_code/planning_constraints.py +904 -0
  328. alysis_code/policy.py +95 -0
  329. alysis_code/preview_server.py +457 -0
  330. alysis_code/process_reaping.py +566 -0
  331. alysis_code/profile_presets.py +1834 -0
  332. alysis_code/profiles.py +666 -0
  333. alysis_code/provider_auth/__init__.py +29 -0
  334. alysis_code/provider_auth/base.py +99 -0
  335. alysis_code/provider_auth/openai_codex.py +951 -0
  336. alysis_code/provider_auth/registry.py +76 -0
  337. alysis_code/provider_auth/store.py +125 -0
  338. alysis_code/provider_diagnostics.py +1209 -0
  339. alysis_code/provider_model_catalog.py +685 -0
  340. alysis_code/provider_telemetry.py +1699 -0
  341. alysis_code/provider_url.py +75 -0
  342. alysis_code/reasoning_contracts.py +911 -0
  343. alysis_code/remote_sync.py +350 -0
  344. alysis_code/replanning.py +1195 -0
  345. alysis_code/repo_scan.py +1152 -0
  346. alysis_code/request_estimation.py +296 -0
  347. alysis_code/review_gate.py +617 -0
  348. alysis_code/run_lock.py +1141 -0
  349. alysis_code/run_outcome.py +58 -0
  350. alysis_code/run_provenance.py +774 -0
  351. alysis_code/run_state.py +445 -0
  352. alysis_code/runtime_artifacts.py +116 -0
  353. alysis_code/runtime_context_features.py +78 -0
  354. alysis_code/runtime_kind.py +52 -0
  355. alysis_code/safety/__init__.py +11 -0
  356. alysis_code/safety/mcp_sanitize.py +29 -0
  357. alysis_code/safety/safe_http.py +297 -0
  358. alysis_code/safety/subagent_report.py +184 -0
  359. alysis_code/sandbox_doctor.py +682 -0
  360. alysis_code/sandbox_runner.py +1025 -0
  361. alysis_code/sandbox_settings.py +423 -0
  362. alysis_code/serialized_paths.py +355 -0
  363. alysis_code/server/__init__.py +3 -0
  364. alysis_code/server/app.py +367 -0
  365. alysis_code/server/auth.py +34 -0
  366. alysis_code/server/job_config.py +30 -0
  367. alysis_code/server/settings.py +215 -0
  368. alysis_code/server/store.py +193 -0
  369. alysis_code/server/worker_runner.py +657 -0
  370. alysis_code/service_persistence.py +355 -0
  371. alysis_code/session_artifacts.py +108 -0
  372. alysis_code/session_metrics.py +331 -0
  373. alysis_code/session_store.py +624 -0
  374. alysis_code/skills/README.md +34 -0
  375. alysis_code/skills/__init__.py +104 -0
  376. alysis_code/skills/conventions.py +84 -0
  377. alysis_code/skills/discovery.py +176 -0
  378. alysis_code/skills/eval_models.py +232 -0
  379. alysis_code/skills/eval_runner.py +372 -0
  380. alysis_code/skills/evals.py +1344 -0
  381. alysis_code/skills/install.py +293 -0
  382. alysis_code/skills/loader.py +118 -0
  383. alysis_code/skills/matching.py +103 -0
  384. alysis_code/skills/models.py +71 -0
  385. alysis_code/skills/paths.py +56 -0
  386. alysis_code/skills/prompting.py +500 -0
  387. alysis_code/skills/scaffold.py +142 -0
  388. alysis_code/skills/state.py +441 -0
  389. alysis_code/skills/transactions.py +125 -0
  390. alysis_code/skills/validation.py +304 -0
  391. alysis_code/step_budget.py +238 -0
  392. alysis_code/subagent_labels.py +49 -0
  393. alysis_code/subagents.py +1072 -0
  394. alysis_code/surface/__init__.py +80 -0
  395. alysis_code/surface/base.py +305 -0
  396. alysis_code/surface/console.py +387 -0
  397. alysis_code/surface/events.py +372 -0
  398. alysis_code/surface/hidden_surface.py +529 -0
  399. alysis_code/surface/noop_surface.py +219 -0
  400. alysis_code/surface/rich_surface.py +1555 -0
  401. alysis_code/surface/styles.py +67 -0
  402. alysis_code/surface/theme.py +455 -0
  403. alysis_code/surface/types.py +100 -0
  404. alysis_code/swarm_backend.py +926 -0
  405. alysis_code/swarm_orchestrator.py +4020 -0
  406. alysis_code/swarm_scheduler.py +441 -0
  407. alysis_code/swarm_trace.py +429 -0
  408. alysis_code/swarm_worker.py +2119 -0
  409. alysis_code/swarm_write_guard.py +348 -0
  410. alysis_code/task_dependencies.py +170 -0
  411. alysis_code/task_readiness.py +992 -0
  412. alysis_code/task_scope.py +2148 -0
  413. alysis_code/terminal_manager.py +762 -0
  414. alysis_code/terminal_ownership.py +460 -0
  415. alysis_code/text_normalization.py +30 -0
  416. alysis_code/token_budget.py +97 -0
  417. alysis_code/tools/README.md +34 -0
  418. alysis_code/tools/__init__.py +1 -0
  419. alysis_code/tools/artifacts.py +127 -0
  420. alysis_code/tools/availability.py +188 -0
  421. alysis_code/tools/fs.py +1456 -0
  422. alysis_code/tools/git.py +461 -0
  423. alysis_code/tools/history.py +229 -0
  424. alysis_code/tools/http_timeout.py +78 -0
  425. alysis_code/tools/image_generation.py +552 -0
  426. alysis_code/tools/registry.py +2936 -0
  427. alysis_code/tools/repo_map.py +476 -0
  428. alysis_code/tools/search.py +563 -0
  429. alysis_code/tools/shell.py +135 -0
  430. alysis_code/tools/symbols.py +1350 -0
  431. alysis_code/tools/test_discovery.py +643 -0
  432. alysis_code/tools/web.py +482 -0
  433. alysis_code/tools/web_search.py +2012 -0
  434. alysis_code/tools/web_search_dashscope.py +557 -0
  435. alysis_code/tools/web_search_ddgs.py +221 -0
  436. alysis_code/tools/web_search_provider_adapters.py +1429 -0
  437. alysis_code/tools/web_search_tavily.py +194 -0
  438. alysis_code/updates.py +933 -0
  439. alysis_code/usage_tracker.py +1990 -0
  440. alysis_code/verification_command_analysis.py +1004 -0
  441. alysis_code/verification_contract.py +574 -0
  442. alysis_code/verification_failure_summary.py +273 -0
  443. alysis_code/verification_repair.py +385 -0
  444. alysis_code/verify_gate.py +3129 -0
  445. alysis_code/web_research.py +1872 -0
  446. alysis_code/web_search_adapters.py +66 -0
  447. alysis_code/web_search_policy.py +27 -0
  448. alysis_code/workspace_binding.py +389 -0
  449. alysis_code/workspace_binding_ui.py +408 -0
  450. alysis_code/workspace_context.py +273 -0
  451. alysis_code/workspace_isolation.py +138 -0
  452. alysis_code/workspace_provisioning.py +455 -0
  453. alysis_code-0.13.0.dist-info/METADATA +507 -0
  454. alysis_code-0.13.0.dist-info/RECORD +458 -0
  455. alysis_code-0.13.0.dist-info/WHEEL +4 -0
  456. alysis_code-0.13.0.dist-info/entry_points.txt +3 -0
  457. alysis_code-0.13.0.dist-info/licenses/LICENSE +176 -0
  458. alysis_code-0.13.0.dist-info/licenses/NOTICE +4 -0
@@ -0,0 +1,367 @@
1
+ import inspect
2
+ import sys
3
+ import tempfile
4
+ from pathlib import Path
5
+ from typing import Literal
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from .auth import require_token
10
+ from .job_config import resolve_effective_model_base_url
11
+ from .settings import ServerSettings
12
+ from .store import ServerStore, ServerStoreError
13
+ from .worker_runner import JobRunner
14
+
15
+ _UPLOAD_CHUNK_BYTES = 1024 * 1024
16
+ _AgentMode = Literal["auto", "review", "readonly", "fullaccess"]
17
+ _SwarmMode = Literal["auto"]
18
+
19
+
20
+ class RunJobRequest(BaseModel):
21
+ instruction: str
22
+ mode: _AgentMode = "auto"
23
+ yes: bool = True
24
+ model: str | None = None
25
+ base_url: str | None = None
26
+ temperature: float | None = None
27
+
28
+
29
+ class ForgeExecRequest(BaseModel):
30
+ task_id: str = Field(min_length=1)
31
+ mode: _AgentMode = "auto"
32
+ yes: bool = True
33
+ model: str | None = None
34
+ base_url: str | None = None
35
+ temperature: float | None = None
36
+
37
+
38
+ class ForgeSwarmRequest(BaseModel):
39
+ # Non-dry-run swarm orchestration is auto-only by runtime invariant.
40
+ mode: _SwarmMode = "auto"
41
+ yes: bool = True
42
+ model: str | None = None
43
+ base_url: str | None = None
44
+ temperature: float | None = None
45
+
46
+
47
+ async def _close_upload_best_effort(upload: object) -> None:
48
+ close = getattr(upload, "close", None)
49
+ if not callable(close):
50
+ return
51
+ try:
52
+ result = close()
53
+ if inspect.isawaitable(result):
54
+ await result
55
+ except Exception:
56
+ return
57
+
58
+
59
+ async def _stage_uploaded_zip(
60
+ *,
61
+ upload: object,
62
+ max_upload_bytes: int,
63
+ chunk_size: int = _UPLOAD_CHUNK_BYTES,
64
+ ) -> Path:
65
+ if chunk_size <= 0:
66
+ raise ValueError("chunk_size must be > 0")
67
+
68
+ temp_path: Path | None = None
69
+ total_bytes = 0
70
+
71
+ try:
72
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".zip") as handle:
73
+ temp_path = Path(handle.name)
74
+ while True:
75
+ chunk = await upload.read(chunk_size) # type: ignore[attr-defined]
76
+ if not chunk:
77
+ break
78
+ next_total = total_bytes + len(chunk)
79
+ if next_total > max_upload_bytes:
80
+ raise ServerStoreError(
81
+ f"Upload too large ({next_total} bytes); max is {max_upload_bytes} bytes."
82
+ )
83
+ handle.write(chunk)
84
+ total_bytes = next_total
85
+ return temp_path
86
+ except Exception:
87
+ if temp_path is not None:
88
+ try:
89
+ temp_path.unlink()
90
+ except OSError:
91
+ pass
92
+ raise
93
+ finally:
94
+ await _close_upload_best_effort(upload)
95
+
96
+
97
+ async def _create_run_from_upload(
98
+ *,
99
+ store: ServerStore,
100
+ upload: object,
101
+ max_upload_bytes: int,
102
+ chunk_size: int = _UPLOAD_CHUNK_BYTES,
103
+ ) -> str:
104
+ # Stage uploads to disk so size enforcement happens during request reads, not after
105
+ # buffering the full multipart body into memory.
106
+ staged_path = await _stage_uploaded_zip(
107
+ upload=upload,
108
+ max_upload_bytes=max_upload_bytes,
109
+ chunk_size=chunk_size,
110
+ )
111
+ try:
112
+ return store.create_run_from_zip_path(staged_path)
113
+ finally:
114
+ try:
115
+ staged_path.unlink()
116
+ except OSError:
117
+ pass
118
+
119
+
120
+ def _append_common_agent_args(
121
+ command: list[str],
122
+ *,
123
+ mode: str,
124
+ yes: bool,
125
+ model: str,
126
+ base_url: str | None,
127
+ temperature: float | None,
128
+ ) -> None:
129
+ command.extend(["--mode", mode, "--api-key-env", "ALYSIS_API_KEY"])
130
+ if yes:
131
+ command.append("--yes")
132
+ command.extend(["--model", model])
133
+ if base_url:
134
+ command.extend(["--base-url", base_url])
135
+ if temperature is not None:
136
+ command.extend(["--temperature", str(temperature)])
137
+
138
+
139
+ def _agent_entrypoint_prefix(settings: ServerSettings) -> list[str]:
140
+ if settings.worker_backend == "docker":
141
+ return ["python", "-m", "alysis_code"]
142
+ return [sys.executable, "-m", "alysis_code"]
143
+
144
+
145
+ def _append_forge_machine_flag(command: list[str], settings: ServerSettings) -> None:
146
+ """Ask Forge workers for the NDJSON event stream so job status is reported, not guessed."""
147
+ if settings.worker_machine_events:
148
+ command.append("--machine")
149
+
150
+
151
+ def _validate_forge_exec_task_id(task_id: str) -> None:
152
+ if task_id.startswith("-"):
153
+ raise ValueError("Invalid task_id: must not start with '-'")
154
+ if any(ch.isspace() for ch in task_id):
155
+ raise ValueError("Invalid task_id: whitespace is not allowed")
156
+
157
+
158
+ def _build_run_job_command(
159
+ *,
160
+ settings: ServerSettings,
161
+ req: RunJobRequest,
162
+ model: str,
163
+ base_url: str | None,
164
+ ) -> list[str]:
165
+ command = _agent_entrypoint_prefix(settings) + ["run", "--path", "/workspace"]
166
+ _append_common_agent_args(
167
+ command,
168
+ mode=req.mode,
169
+ yes=req.yes,
170
+ model=model,
171
+ base_url=base_url,
172
+ temperature=req.temperature,
173
+ )
174
+ # End-of-options sentinel to prevent instruction option injection.
175
+ command.extend(["--", req.instruction])
176
+ return command
177
+
178
+
179
+ def _build_forge_exec_job_command(
180
+ *,
181
+ settings: ServerSettings,
182
+ req: ForgeExecRequest,
183
+ model: str,
184
+ base_url: str | None,
185
+ ) -> list[str]:
186
+ _validate_forge_exec_task_id(req.task_id)
187
+ command = _agent_entrypoint_prefix(settings) + [
188
+ "forge",
189
+ "exec",
190
+ req.task_id,
191
+ "--path",
192
+ "/workspace",
193
+ ]
194
+ _append_forge_machine_flag(command, settings)
195
+ _append_common_agent_args(
196
+ command,
197
+ mode=req.mode,
198
+ yes=req.yes,
199
+ model=model,
200
+ base_url=base_url,
201
+ temperature=req.temperature,
202
+ )
203
+ return command
204
+
205
+
206
+ def _build_forge_swarm_job_command(
207
+ *,
208
+ settings: ServerSettings,
209
+ req: ForgeSwarmRequest,
210
+ model: str,
211
+ base_url: str | None,
212
+ ) -> list[str]:
213
+ command = _agent_entrypoint_prefix(settings) + [
214
+ "forge",
215
+ "swarm",
216
+ "--path",
217
+ "/workspace",
218
+ ]
219
+ _append_forge_machine_flag(command, settings)
220
+ _append_common_agent_args(
221
+ command,
222
+ mode=req.mode,
223
+ yes=req.yes,
224
+ model=model,
225
+ base_url=base_url,
226
+ temperature=req.temperature,
227
+ )
228
+ return command
229
+
230
+
231
+ def create_app(settings: ServerSettings): # type: ignore[no-untyped-def]
232
+ from fastapi import Depends, FastAPI, File, HTTPException, UploadFile, status
233
+ from fastapi.responses import PlainTextResponse
234
+
235
+ app = FastAPI(title="alysis-code server", version="1.0")
236
+ store = ServerStore(settings)
237
+ runner = JobRunner(settings, store)
238
+ auth_dep = require_token(settings)
239
+
240
+ @app.get("/health")
241
+ def health() -> dict[str, bool]:
242
+ return {"ok": True}
243
+
244
+ @app.post("/v1/runs", dependencies=[Depends(auth_dep)])
245
+ async def create_run(file: UploadFile = File(...)) -> dict[str, str]:
246
+ try:
247
+ run_id = await _create_run_from_upload(
248
+ store=store,
249
+ upload=file,
250
+ max_upload_bytes=settings.max_upload_bytes,
251
+ )
252
+ except ServerStoreError as e:
253
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) from e
254
+ return {"run_id": run_id}
255
+
256
+ @app.post("/v1/runs/empty", dependencies=[Depends(auth_dep)])
257
+ def create_empty_run() -> dict[str, str]:
258
+ run_id = store.create_empty_run()
259
+ return {"run_id": run_id}
260
+
261
+ @app.post("/v1/runs/{run_id}/jobs/run", dependencies=[Depends(auth_dep)])
262
+ def start_run_job(run_id: str, req: RunJobRequest) -> dict[str, str]:
263
+ try:
264
+ effective_model, effective_base_url = resolve_effective_model_base_url(
265
+ settings=settings,
266
+ requested_model=req.model,
267
+ requested_base_url=req.base_url,
268
+ )
269
+ command = _build_run_job_command(
270
+ settings=settings,
271
+ req=req,
272
+ model=effective_model,
273
+ base_url=effective_base_url,
274
+ )
275
+ except ValueError as e:
276
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) from e
277
+
278
+ try:
279
+ job_id = runner.start_job(run_id=run_id, command=command)
280
+ except ServerStoreError as e:
281
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) from e
282
+ return {"job_id": job_id}
283
+
284
+ @app.post("/v1/runs/{run_id}/jobs/forge_exec", dependencies=[Depends(auth_dep)])
285
+ def start_forge_exec_job(run_id: str, req: ForgeExecRequest) -> dict[str, str]:
286
+ try:
287
+ effective_model, effective_base_url = resolve_effective_model_base_url(
288
+ settings=settings,
289
+ requested_model=req.model,
290
+ requested_base_url=req.base_url,
291
+ )
292
+ command = _build_forge_exec_job_command(
293
+ settings=settings,
294
+ req=req,
295
+ model=effective_model,
296
+ base_url=effective_base_url,
297
+ )
298
+ except ValueError as e:
299
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) from e
300
+
301
+ try:
302
+ job_id = runner.start_job(run_id=run_id, command=command)
303
+ except ServerStoreError as e:
304
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) from e
305
+ return {"job_id": job_id}
306
+
307
+ @app.post("/v1/runs/{run_id}/jobs/forge_swarm", dependencies=[Depends(auth_dep)])
308
+ def start_forge_swarm_job(run_id: str, req: ForgeSwarmRequest) -> dict[str, str]:
309
+ try:
310
+ effective_model, effective_base_url = resolve_effective_model_base_url(
311
+ settings=settings,
312
+ requested_model=req.model,
313
+ requested_base_url=req.base_url,
314
+ )
315
+ command = _build_forge_swarm_job_command(
316
+ settings=settings,
317
+ req=req,
318
+ model=effective_model,
319
+ base_url=effective_base_url,
320
+ )
321
+ except ValueError as e:
322
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) from e
323
+
324
+ try:
325
+ job_id = runner.start_job(run_id=run_id, command=command)
326
+ except ServerStoreError as e:
327
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) from e
328
+ return {"job_id": job_id}
329
+
330
+ @app.get("/v1/jobs/{job_id}", dependencies=[Depends(auth_dep)])
331
+ def get_job_status(job_id: str) -> dict[str, object]:
332
+ try:
333
+ status_obj = runner.get_status(job_id)
334
+ except ServerStoreError as e:
335
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) from e
336
+ return {
337
+ "job_id": status_obj.job_id,
338
+ "run_id": status_obj.run_id,
339
+ "status": status_obj.status,
340
+ # How the status was decided: the worker's terminal event, or the exit code.
341
+ "status_source": status_obj.status_source,
342
+ "created_at": status_obj.created_at,
343
+ "started_at": status_obj.started_at,
344
+ "finished_at": status_obj.finished_at,
345
+ "exit_code": status_obj.exit_code,
346
+ "error": status_obj.error,
347
+ "terminal_event": status_obj.terminal_event,
348
+ }
349
+
350
+ @app.get("/v1/jobs/{job_id}/logs", dependencies=[Depends(auth_dep)])
351
+ def get_job_logs(job_id: str) -> PlainTextResponse:
352
+ try:
353
+ body = runner.read_logs(job_id)
354
+ except ServerStoreError as e:
355
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) from e
356
+ return PlainTextResponse(body)
357
+
358
+ @app.post("/v1/jobs/{job_id}/cancel", dependencies=[Depends(auth_dep)])
359
+ def cancel_job(job_id: str) -> dict[str, str]:
360
+ try:
361
+ runner.cancel_job(job_id)
362
+ status_obj = runner.get_status(job_id)
363
+ except ServerStoreError as e:
364
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) from e
365
+ return {"job_id": status_obj.job_id, "status": status_obj.status}
366
+
367
+ return app
@@ -0,0 +1,34 @@
1
+ from collections.abc import Callable
2
+
3
+ from .settings import ServerSettings
4
+
5
+ _LOCAL_CLIENTS = {"127.0.0.1", "::1", "localhost"}
6
+
7
+
8
+ def require_token(settings: ServerSettings) -> Callable[..., None]:
9
+ from fastapi import HTTPException, Request, status
10
+
11
+ async def _dependency(request: Request) -> None:
12
+ client_host = (request.client.host if request.client else "") or ""
13
+ if settings.token:
14
+ auth_value = request.headers.get("authorization") or ""
15
+ if not auth_value.lower().startswith("bearer "):
16
+ raise HTTPException(
17
+ status_code=status.HTTP_401_UNAUTHORIZED,
18
+ detail="Missing Bearer token.",
19
+ )
20
+ token = auth_value[7:].strip()
21
+ if token != settings.token:
22
+ raise HTTPException(
23
+ status_code=status.HTTP_403_FORBIDDEN,
24
+ detail="Invalid token.",
25
+ )
26
+ return
27
+
28
+ if client_host not in _LOCAL_CLIENTS:
29
+ raise HTTPException(
30
+ status_code=status.HTTP_403_FORBIDDEN,
31
+ detail="Server token is not set; only localhost clients are allowed.",
32
+ )
33
+
34
+ return _dependency
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from .settings import ServerSettings
4
+
5
+
6
+ def resolve_effective_model_base_url(
7
+ settings: ServerSettings,
8
+ requested_model: str | None,
9
+ requested_base_url: str | None,
10
+ ) -> tuple[str, str | None]:
11
+ req_model = (requested_model or "").strip() or None
12
+ req_base_url = (requested_base_url or "").strip() or None
13
+
14
+ if settings.default_model:
15
+ model = settings.default_model
16
+ elif settings.allow_client_model and req_model:
17
+ model = req_model
18
+ else:
19
+ raise ValueError("Model is required (set ALYSIS_SERVER_MODEL or provide request.model).")
20
+
21
+ if settings.default_base_url:
22
+ base_url = settings.default_base_url
23
+ elif settings.allow_client_base_url and req_base_url:
24
+ if not req_base_url.startswith(("http://", "https://")):
25
+ raise ValueError("Client base_url must use http:// or https://.")
26
+ base_url = req_base_url
27
+ else:
28
+ base_url = None
29
+
30
+ return model, base_url
@@ -0,0 +1,215 @@
1
+ from __future__ import annotations
2
+
3
+ import platform
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+ from ..branding import canonical_server_data_dir, env_get
8
+ from ..config import ConfigError
9
+
10
+ _LOCAL_HOSTS = {"127.0.0.1", "::1", "localhost"}
11
+ _WORKER_BACKENDS = {"bwrap", "docker"}
12
+ _SANDBOX_MODES = {"off", "warn", "strict"}
13
+ _NETWORK_MODES = {"off", "on"}
14
+ _DEFAULT_MAX_UPLOAD_BYTES = 50 * 1024 * 1024
15
+
16
+
17
+ def _parse_int(raw: object, *, field_name: str, default: int, min_value: int) -> int:
18
+ if raw is None:
19
+ return default
20
+ try:
21
+ value = int(str(raw).strip())
22
+ except ValueError as e:
23
+ raise ConfigError(f"Invalid {field_name}: {raw!r}. Expected integer.") from e
24
+ if value < min_value:
25
+ raise ConfigError(f"Invalid {field_name}: {raw!r}. Expected >= {min_value}.")
26
+ return value
27
+
28
+
29
+ def _parse_choice(raw: object, *, field_name: str, default: str, allowed: set[str]) -> str:
30
+ if raw is None:
31
+ return default
32
+ value = str(raw).strip().lower()
33
+ if value in allowed:
34
+ return value
35
+ options = ", ".join(sorted(allowed))
36
+ raise ConfigError(f"Invalid {field_name}: {raw!r}. Expected one of: {options}")
37
+
38
+
39
+ def _parse_bool(raw: object, *, field_name: str, default: bool) -> bool:
40
+ if raw is None:
41
+ return default
42
+ if isinstance(raw, bool):
43
+ return raw
44
+ value = str(raw).strip().lower()
45
+ if value in {"1", "true", "yes", "on"}:
46
+ return True
47
+ if value in {"0", "false", "no", "off"}:
48
+ return False
49
+ raise ConfigError(f"Invalid {field_name}: {raw!r}. Expected one of: 0, 1")
50
+
51
+
52
+ def _parse_optional_string(raw: object) -> str | None:
53
+ if raw is None:
54
+ return None
55
+ value = str(raw).strip()
56
+ return value or None
57
+
58
+
59
+ def _parse_optional_base_url(raw: object, *, field_name: str) -> str | None:
60
+ value = _parse_optional_string(raw)
61
+ if value is None:
62
+ return None
63
+ if value.startswith(("http://", "https://")):
64
+ return value
65
+ raise ConfigError(f"Invalid {field_name}: {value!r}. Expected http:// or https:// URL.")
66
+
67
+
68
+ def _default_data_dir() -> Path:
69
+ return canonical_server_data_dir()
70
+
71
+
72
+ def _default_worker_backend() -> str:
73
+ if platform.system().lower() == "linux":
74
+ return "bwrap"
75
+ return "docker"
76
+
77
+
78
+ @dataclass(frozen=True)
79
+ class ServerSettings:
80
+ host: str
81
+ port: int
82
+ data_dir: Path
83
+ token: str | None
84
+ max_upload_bytes: int
85
+ max_concurrent_jobs: int
86
+ worker_backend: str
87
+ worker_sandbox_mode: str
88
+ worker_network: str
89
+ default_model: str | None
90
+ default_base_url: str | None
91
+ allow_client_model: bool
92
+ allow_client_base_url: bool
93
+ worker_machine_events: bool = True
94
+
95
+ @property
96
+ def allow_unauthenticated_localhost_only(self) -> bool:
97
+ return self.token is None
98
+
99
+ @property
100
+ def host_is_local(self) -> bool:
101
+ return self.host.strip().lower() in _LOCAL_HOSTS
102
+
103
+ @property
104
+ def server_model(self) -> str | None:
105
+ return self.default_model
106
+
107
+ @property
108
+ def server_base_url(self) -> str | None:
109
+ return self.default_base_url
110
+
111
+ @property
112
+ def allow_client_model_override(self) -> bool:
113
+ return self.allow_client_model
114
+
115
+ @property
116
+ def allow_client_base_url_override(self) -> bool:
117
+ return self.allow_client_base_url
118
+
119
+
120
+ def resolve_server_settings(
121
+ *,
122
+ host: str = "127.0.0.1",
123
+ port: int = 7070,
124
+ data_dir: Path | None = None,
125
+ ) -> ServerSettings:
126
+ host_value = host.strip() or "127.0.0.1"
127
+ port_value = _parse_int(port, field_name="port", default=7070, min_value=1)
128
+ if port_value > 65535:
129
+ raise ConfigError(f"Invalid port: {port_value}. Expected <= 65535.")
130
+
131
+ env_data_dir = str(env_get("ALYSIS_SERVER_DATA_DIR") or "").strip()
132
+ if data_dir is not None:
133
+ data_path = data_dir.expanduser()
134
+ elif env_data_dir:
135
+ data_path = Path(env_data_dir).expanduser()
136
+ else:
137
+ data_path = _default_data_dir()
138
+
139
+ token_raw = str(env_get("ALYSIS_SERVER_TOKEN") or "").strip()
140
+ token = token_raw or None
141
+
142
+ max_upload_bytes = _parse_int(
143
+ env_get("ALYSIS_SERVER_MAX_UPLOAD_BYTES"),
144
+ field_name="ALYSIS_SERVER_MAX_UPLOAD_BYTES",
145
+ default=_DEFAULT_MAX_UPLOAD_BYTES,
146
+ min_value=1024,
147
+ )
148
+ max_concurrent_jobs = _parse_int(
149
+ env_get("ALYSIS_SERVER_MAX_JOBS"),
150
+ field_name="ALYSIS_SERVER_MAX_JOBS",
151
+ default=2,
152
+ min_value=1,
153
+ )
154
+
155
+ worker_backend = _parse_choice(
156
+ env_get("ALYSIS_SERVER_WORKER_BACKEND"),
157
+ field_name="ALYSIS_SERVER_WORKER_BACKEND",
158
+ default=_default_worker_backend(),
159
+ allowed=_WORKER_BACKENDS,
160
+ )
161
+ if worker_backend == "bwrap" and platform.system().lower() != "linux":
162
+ raise ConfigError("ALYSIS_SERVER_WORKER_BACKEND=bwrap requires Linux. Use docker instead.")
163
+
164
+ worker_sandbox_default = "strict"
165
+ worker_sandbox_mode = _parse_choice(
166
+ env_get("ALYSIS_SERVER_WORKER_SANDBOX_MODE"),
167
+ field_name="ALYSIS_SERVER_WORKER_SANDBOX_MODE",
168
+ default=worker_sandbox_default,
169
+ allowed=_SANDBOX_MODES,
170
+ )
171
+ worker_network = _parse_choice(
172
+ env_get("ALYSIS_SERVER_WORKER_NETWORK"),
173
+ field_name="ALYSIS_SERVER_WORKER_NETWORK",
174
+ default="on",
175
+ allowed=_NETWORK_MODES,
176
+ )
177
+ default_model = _parse_optional_string(env_get("ALYSIS_SERVER_MODEL"))
178
+ default_base_url = _parse_optional_base_url(
179
+ env_get("ALYSIS_SERVER_BASE_URL"),
180
+ field_name="ALYSIS_SERVER_BASE_URL",
181
+ )
182
+ allow_client_base_url = _parse_bool(
183
+ env_get("ALYSIS_SERVER_ALLOW_CLIENT_BASE_URL"),
184
+ field_name="ALYSIS_SERVER_ALLOW_CLIENT_BASE_URL",
185
+ default=False,
186
+ )
187
+ allow_client_model = _parse_bool(
188
+ env_get("ALYSIS_SERVER_ALLOW_CLIENT_MODEL"),
189
+ field_name="ALYSIS_SERVER_ALLOW_CLIENT_MODEL",
190
+ default=True,
191
+ )
192
+ # Forge worker jobs run with --machine so job status comes from the worker's own
193
+ # terminal event instead of being inferred from an exit code.
194
+ worker_machine_events = _parse_bool(
195
+ env_get("ALYSIS_SERVER_MACHINE_EVENTS"),
196
+ field_name="ALYSIS_SERVER_MACHINE_EVENTS",
197
+ default=True,
198
+ )
199
+
200
+ return ServerSettings(
201
+ host=host_value,
202
+ port=port_value,
203
+ data_dir=data_path.resolve(),
204
+ token=token,
205
+ max_upload_bytes=max_upload_bytes,
206
+ max_concurrent_jobs=max_concurrent_jobs,
207
+ worker_backend=worker_backend,
208
+ worker_sandbox_mode=worker_sandbox_mode,
209
+ worker_network=worker_network,
210
+ default_model=default_model,
211
+ default_base_url=default_base_url,
212
+ allow_client_model=allow_client_model,
213
+ allow_client_base_url=allow_client_base_url,
214
+ worker_machine_events=worker_machine_events,
215
+ )