langchain-agentx-python 0.1__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 (354) hide show
  1. langchain_agentx/__init__.py +46 -0
  2. langchain_agentx/command/__init__.py +28 -0
  3. langchain_agentx/command/builtin/__init__.py +25 -0
  4. langchain_agentx/command/builtin/clear.py +33 -0
  5. langchain_agentx/command/builtin/compact.py +33 -0
  6. langchain_agentx/command/builtin/memory.py +37 -0
  7. langchain_agentx/command/builtin/reload_plugins.py +42 -0
  8. langchain_agentx/command/context.py +30 -0
  9. langchain_agentx/command/dispatcher.py +183 -0
  10. langchain_agentx/command/registry.py +110 -0
  11. langchain_agentx/command/result.py +25 -0
  12. langchain_agentx/command/types.py +41 -0
  13. langchain_agentx/config/__init__.py +14 -0
  14. langchain_agentx/loop/__init__.py +47 -0
  15. langchain_agentx/loop/config/__init__.py +20 -0
  16. langchain_agentx/loop/config/agent_config.py +66 -0
  17. langchain_agentx/loop/config/agent_loop_config.py +72 -0
  18. langchain_agentx/loop/config/model_context_resolver.py +105 -0
  19. langchain_agentx/loop/config/runtime_settings.py +50 -0
  20. langchain_agentx/loop/config/token_estimator.py +133 -0
  21. langchain_agentx/loop/context/__init__.py +66 -0
  22. langchain_agentx/loop/context/blocking_guard.py +97 -0
  23. langchain_agentx/loop/context/compaction_service.py +60 -0
  24. langchain_agentx/loop/context/message_utils.py +56 -0
  25. langchain_agentx/loop/context/pipeline.py +127 -0
  26. langchain_agentx/loop/context/settings.py +103 -0
  27. langchain_agentx/loop/context/stages/__init__.py +29 -0
  28. langchain_agentx/loop/context/stages/autocompact.py +140 -0
  29. langchain_agentx/loop/context/stages/base.py +32 -0
  30. langchain_agentx/loop/context/stages/collapse.py +76 -0
  31. langchain_agentx/loop/context/stages/microcompact.py +76 -0
  32. langchain_agentx/loop/context/stages/noop.py +33 -0
  33. langchain_agentx/loop/context/stages/snip.py +71 -0
  34. langchain_agentx/loop/context/stages/tool_result_budget.py +69 -0
  35. langchain_agentx/loop/context/types.py +79 -0
  36. langchain_agentx/loop/exit/__init__.py +1 -0
  37. langchain_agentx/loop/exit/exit_logic.py +320 -0
  38. langchain_agentx/loop/exit/reason_codes.py +39 -0
  39. langchain_agentx/loop/graph/__init__.py +5 -0
  40. langchain_agentx/loop/graph/builtin_loop_control.py +197 -0
  41. langchain_agentx/loop/graph/factory.py +1409 -0
  42. langchain_agentx/loop/graph/graph_edges.py +820 -0
  43. langchain_agentx/loop/hook/__init__.py +48 -0
  44. langchain_agentx/loop/hook/async_hook_runner.py +62 -0
  45. langchain_agentx/loop/hook/config.py +280 -0
  46. langchain_agentx/loop/hook/engine.py +321 -0
  47. langchain_agentx/loop/hook/executors/__init__.py +9 -0
  48. langchain_agentx/loop/hook/executors/agent.py +107 -0
  49. langchain_agentx/loop/hook/executors/command.py +230 -0
  50. langchain_agentx/loop/hook/executors/http.py +114 -0
  51. langchain_agentx/loop/hook/executors/prompt.py +92 -0
  52. langchain_agentx/loop/hook/graph_wiring.py +134 -0
  53. langchain_agentx/loop/hook/registry.py +262 -0
  54. langchain_agentx/loop/hook/trust.py +43 -0
  55. langchain_agentx/loop/hook/types.py +110 -0
  56. langchain_agentx/loop/injection/__init__.py +13 -0
  57. langchain_agentx/loop/injection/dedup.py +74 -0
  58. langchain_agentx/loop/loop_abort.py +36 -0
  59. langchain_agentx/loop/model/__init__.py +1 -0
  60. langchain_agentx/loop/model/model_node.py +648 -0
  61. langchain_agentx/loop/model/model_nodes.py +661 -0
  62. langchain_agentx/loop/model/orphan_tool_results.py +38 -0
  63. langchain_agentx/loop/model/retrier.py +307 -0
  64. langchain_agentx/loop/model/retry_bridge.py +58 -0
  65. langchain_agentx/loop/model/retry_events.py +35 -0
  66. langchain_agentx/loop/model/retry_policy.py +56 -0
  67. langchain_agentx/loop/model/schema_and_format.py +153 -0
  68. langchain_agentx/loop/model/tool_and_model_binding.py +227 -0
  69. langchain_agentx/loop/model/tool_call_degradation_corrector.py +443 -0
  70. langchain_agentx/loop/model/tool_transcript_guard.py +225 -0
  71. langchain_agentx/loop/prompt/__init__.py +95 -0
  72. langchain_agentx/loop/prompt/builder.py +61 -0
  73. langchain_agentx/loop/prompt/builtin.py +218 -0
  74. langchain_agentx/loop/prompt/compact.py +408 -0
  75. langchain_agentx/loop/prompt/sections.py +120 -0
  76. langchain_agentx/loop/runtime/__init__.py +19 -0
  77. langchain_agentx/loop/runtime/context.py +34 -0
  78. langchain_agentx/loop/runtime/context_factory.py +107 -0
  79. langchain_agentx/loop/runtime/subagent_execution_paths.py +68 -0
  80. langchain_agentx/loop/subagent/__init__.py +53 -0
  81. langchain_agentx/loop/subagent/async_runner.py +215 -0
  82. langchain_agentx/loop/subagent/context.py +209 -0
  83. langchain_agentx/loop/subagent/fork_worktree_notice.py +25 -0
  84. langchain_agentx/loop/subagent/graph.py +72 -0
  85. langchain_agentx/loop/subagent/orchestrator.py +391 -0
  86. langchain_agentx/loop/subagent/progress.py +30 -0
  87. langchain_agentx/loop/subagent/prompt.py +52 -0
  88. langchain_agentx/loop/subagent/runner.py +504 -0
  89. langchain_agentx/loop/subagent/transcript.py +172 -0
  90. langchain_agentx/memory/__init__.py +2 -0
  91. langchain_agentx/memory/instruction/__init__.py +12 -0
  92. langchain_agentx/memory/instruction/loader.py +325 -0
  93. langchain_agentx/memory/instruction/resolver.py +24 -0
  94. langchain_agentx/memory/instruction/runtime.py +83 -0
  95. langchain_agentx/memory/instruction/sections.py +83 -0
  96. langchain_agentx/memory/instruction/types.py +59 -0
  97. langchain_agentx/memory/memdir/__init__.py +77 -0
  98. langchain_agentx/memory/memdir/age.py +36 -0
  99. langchain_agentx/memory/memdir/agent_memory.py +380 -0
  100. langchain_agentx/memory/memdir/extractor.py +309 -0
  101. langchain_agentx/memory/memdir/loader.py +187 -0
  102. langchain_agentx/memory/memdir/paths.py +63 -0
  103. langchain_agentx/memory/memdir/recall.py +45 -0
  104. langchain_agentx/memory/memdir/runtime.py +43 -0
  105. langchain_agentx/memory/memdir/scan.py +135 -0
  106. langchain_agentx/memory/memdir/types.py +104 -0
  107. langchain_agentx/memory/session/__init__.py +76 -0
  108. langchain_agentx/memory/session/compact_bridge.py +208 -0
  109. langchain_agentx/memory/session/prompts.py +172 -0
  110. langchain_agentx/memory/session/session_memory.py +282 -0
  111. langchain_agentx/observability/__init__.py +67 -0
  112. langchain_agentx/observability/evaluation/__init__.py +17 -0
  113. langchain_agentx/observability/evaluation/checkers/__init__.py +18 -0
  114. langchain_agentx/observability/evaluation/checkers/base.py +34 -0
  115. langchain_agentx/observability/evaluation/checkers/compaction.py +38 -0
  116. langchain_agentx/observability/evaluation/checkers/degradation.py +50 -0
  117. langchain_agentx/observability/evaluation/checkers/exit_quality.py +42 -0
  118. langchain_agentx/observability/evaluation/checkers/session_memory.py +45 -0
  119. langchain_agentx/observability/evaluation/checkers/tool_behavior.py +53 -0
  120. langchain_agentx/observability/evaluation/retention_scheduler.py +67 -0
  121. langchain_agentx/observability/evaluation/service.py +102 -0
  122. langchain_agentx/observability/evaluation/state.py +32 -0
  123. langchain_agentx/observability/evaluation/store.py +258 -0
  124. langchain_agentx/observability/events/__init__.py +15 -0
  125. langchain_agentx/observability/events/langchain_agentx_event_adapter.py +832 -0
  126. langchain_agentx/observability/logging/__init__.py +15 -0
  127. langchain_agentx/observability/logging/debug_burst.py +95 -0
  128. langchain_agentx/observability/logging/logging_config.py +178 -0
  129. langchain_agentx/observability/logging/logging_contract.py +65 -0
  130. langchain_agentx/observability/replay/__init__.py +35 -0
  131. langchain_agentx/observability/replay/cli.py +91 -0
  132. langchain_agentx/observability/replay/service.py +83 -0
  133. langchain_agentx/observability/replay/store.py +278 -0
  134. langchain_agentx/observability/replay/ui.py +47 -0
  135. langchain_agentx/observability/trace/__init__.py +25 -0
  136. langchain_agentx/observability/trace/collector.py +560 -0
  137. langchain_agentx/observability/trace/event_emitter.py +183 -0
  138. langchain_agentx/observability/trace/hook_event_emitter.py +49 -0
  139. langchain_agentx/observability/trace/models.py +144 -0
  140. langchain_agentx/observability/trace/sqlite_store.py +873 -0
  141. langchain_agentx/observability/trace/trace_callback.py +295 -0
  142. langchain_agentx/observability/trace/trace_lifecycle_collector.py +114 -0
  143. langchain_agentx/plugin/__init__.py +26 -0
  144. langchain_agentx/plugin/builtin.py +53 -0
  145. langchain_agentx/plugin/config.py +113 -0
  146. langchain_agentx/plugin/loader.py +386 -0
  147. langchain_agentx/plugin/manifest.py +154 -0
  148. langchain_agentx/plugin/registries.py +211 -0
  149. langchain_agentx/plugin/types.py +142 -0
  150. langchain_agentx/provider/__init__.py +27 -0
  151. langchain_agentx/provider/anthropic.py +121 -0
  152. langchain_agentx/provider/compatible_chat_openai.py +86 -0
  153. langchain_agentx/provider/env.py +45 -0
  154. langchain_agentx/provider/model_profile.py +156 -0
  155. langchain_agentx/provider/openai.py +89 -0
  156. langchain_agentx/session/__init__.py +17 -0
  157. langchain_agentx/session/agent_session.py +320 -0
  158. langchain_agentx/session/conversation_factory.py +87 -0
  159. langchain_agentx/session/conversation_recovery.py +156 -0
  160. langchain_agentx/session/conversation_session.py +198 -0
  161. langchain_agentx/session/factory.py +143 -0
  162. langchain_agentx/session/protocol.py +25 -0
  163. langchain_agentx/task_runtime/__init__.py +113 -0
  164. langchain_agentx/task_runtime/core/__init__.py +51 -0
  165. langchain_agentx/task_runtime/core/ids.py +33 -0
  166. langchain_agentx/task_runtime/core/interfaces.py +115 -0
  167. langchain_agentx/task_runtime/core/notification_priority.py +19 -0
  168. langchain_agentx/task_runtime/core/types.py +136 -0
  169. langchain_agentx/task_runtime/integrations/__init__.py +33 -0
  170. langchain_agentx/task_runtime/integrations/loop_adapter.py +91 -0
  171. langchain_agentx/task_runtime/integrations/loop_integration.py +61 -0
  172. langchain_agentx/task_runtime/integrations/prefetch_providers.py +108 -0
  173. langchain_agentx/task_runtime/integrations/provider_factory.py +103 -0
  174. langchain_agentx/task_runtime/integrations/queued_command_provider.py +184 -0
  175. langchain_agentx/task_runtime/integrations/sqlite_queued_command_provider.py +338 -0
  176. langchain_agentx/task_runtime/integrations/tool_use_summary_provider.py +254 -0
  177. langchain_agentx/task_runtime/orchestrator/__init__.py +5 -0
  178. langchain_agentx/task_runtime/orchestrator/runtime.py +386 -0
  179. langchain_agentx/task_runtime/output/__init__.py +5 -0
  180. langchain_agentx/task_runtime/output/sink.py +64 -0
  181. langchain_agentx/task_runtime/policy/__init__.py +11 -0
  182. langchain_agentx/task_runtime/policy/withhold_visibility.py +32 -0
  183. langchain_agentx/task_runtime/queue/__init__.py +5 -0
  184. langchain_agentx/task_runtime/queue/in_memory.py +55 -0
  185. langchain_agentx/task_runtime/skill_prefetch/__init__.py +4 -0
  186. langchain_agentx/task_runtime/skill_prefetch/attachments.py +46 -0
  187. langchain_agentx/task_runtime/skill_prefetch/models.py +37 -0
  188. langchain_agentx/task_runtime/skill_prefetch/provider.py +344 -0
  189. langchain_agentx/task_runtime/store/__init__.py +6 -0
  190. langchain_agentx/task_runtime/store/in_memory.py +81 -0
  191. langchain_agentx/task_runtime/store/sqlite_store.py +281 -0
  192. langchain_agentx/task_runtime/tasks/__init__.py +76 -0
  193. langchain_agentx/task_runtime/tasks/ai_analysis/__init__.py +15 -0
  194. langchain_agentx/task_runtime/tasks/ai_analysis/base.py +41 -0
  195. langchain_agentx/task_runtime/tasks/ai_analysis/evaluation.py +67 -0
  196. langchain_agentx/task_runtime/tasks/ai_analysis/registry.py +36 -0
  197. langchain_agentx/task_runtime/tasks/ai_analysis/scheduler.py +70 -0
  198. langchain_agentx/task_runtime/tasks/base/__init__.py +6 -0
  199. langchain_agentx/task_runtime/tasks/base/contracts.py +24 -0
  200. langchain_agentx/task_runtime/tasks/custom/__init__.py +7 -0
  201. langchain_agentx/task_runtime/tasks/custom/executor.py +60 -0
  202. langchain_agentx/task_runtime/tasks/custom/notification.py +7 -0
  203. langchain_agentx/task_runtime/tasks/custom/semantics.py +13 -0
  204. langchain_agentx/task_runtime/tasks/custom/spec.py +33 -0
  205. langchain_agentx/task_runtime/tasks/dream_task/__init__.py +15 -0
  206. langchain_agentx/task_runtime/tasks/dream_task/executor.py +61 -0
  207. langchain_agentx/task_runtime/tasks/dream_task/notification.py +19 -0
  208. langchain_agentx/task_runtime/tasks/dream_task/semantics.py +13 -0
  209. langchain_agentx/task_runtime/tasks/dream_task/spec.py +35 -0
  210. langchain_agentx/task_runtime/tasks/dream_task/state.py +17 -0
  211. langchain_agentx/task_runtime/tasks/in_process_teammate/__init__.py +12 -0
  212. langchain_agentx/task_runtime/tasks/in_process_teammate/executor.py +36 -0
  213. langchain_agentx/task_runtime/tasks/in_process_teammate/notification.py +25 -0
  214. langchain_agentx/task_runtime/tasks/in_process_teammate/semantics.py +13 -0
  215. langchain_agentx/task_runtime/tasks/in_process_teammate/spec.py +63 -0
  216. langchain_agentx/task_runtime/tasks/local_agent/__init__.py +14 -0
  217. langchain_agentx/task_runtime/tasks/local_agent/executor.py +33 -0
  218. langchain_agentx/task_runtime/tasks/local_agent/notification.py +21 -0
  219. langchain_agentx/task_runtime/tasks/local_agent/runner.py +43 -0
  220. langchain_agentx/task_runtime/tasks/local_agent/semantics.py +13 -0
  221. langchain_agentx/task_runtime/tasks/local_agent/spec.py +31 -0
  222. langchain_agentx/task_runtime/tasks/local_bash/__init__.py +13 -0
  223. langchain_agentx/task_runtime/tasks/local_bash/executor.py +95 -0
  224. langchain_agentx/task_runtime/tasks/local_bash/notification.py +22 -0
  225. langchain_agentx/task_runtime/tasks/local_bash/semantics.py +13 -0
  226. langchain_agentx/task_runtime/tasks/local_bash/spec.py +55 -0
  227. langchain_agentx/task_runtime/tasks/remote_agent/__init__.py +19 -0
  228. langchain_agentx/task_runtime/tasks/remote_agent/backend.py +76 -0
  229. langchain_agentx/task_runtime/tasks/remote_agent/executor.py +37 -0
  230. langchain_agentx/task_runtime/tasks/remote_agent/notification.py +22 -0
  231. langchain_agentx/task_runtime/tasks/remote_agent/semantics.py +13 -0
  232. langchain_agentx/task_runtime/tasks/remote_agent/spec.py +34 -0
  233. langchain_agentx/task_runtime/tasks/trace_cleanup/__init__.py +19 -0
  234. langchain_agentx/task_runtime/tasks/trace_cleanup/bootstrap.py +95 -0
  235. langchain_agentx/task_runtime/tasks/trace_cleanup/executor.py +66 -0
  236. langchain_agentx/task_runtime/tasks/trace_cleanup/scheduler.py +169 -0
  237. langchain_agentx/tool_runtime/__init__.py +90 -0
  238. langchain_agentx/tool_runtime/adapter.py +365 -0
  239. langchain_agentx/tool_runtime/base.py +319 -0
  240. langchain_agentx/tool_runtime/errors.py +190 -0
  241. langchain_agentx/tool_runtime/identical_call_cache.py +110 -0
  242. langchain_agentx/tool_runtime/loader.py +195 -0
  243. langchain_agentx/tool_runtime/models.py +260 -0
  244. langchain_agentx/tool_runtime/permission_context.py +78 -0
  245. langchain_agentx/tool_runtime/pipeline.py +621 -0
  246. langchain_agentx/tool_runtime/policy.py +447 -0
  247. langchain_agentx/tool_runtime/registry.py +81 -0
  248. langchain_agentx/tool_runtime/resolvers/__init__.py +27 -0
  249. langchain_agentx/tool_runtime/resolvers/agent_session.py +125 -0
  250. langchain_agentx/tool_runtime/resolvers/background.py +32 -0
  251. langchain_agentx/tool_runtime/resolvers/base.py +20 -0
  252. langchain_agentx/tool_runtime/resolvers/conversation.py +22 -0
  253. langchain_agentx/tool_runtime/resolvers/workflow.py +73 -0
  254. langchain_agentx/tool_runtime/session_store.py +132 -0
  255. langchain_agentx/tool_runtime/smoke_test_runtime.py +294 -0
  256. langchain_agentx/tool_runtime/state_bridge.py +164 -0
  257. langchain_agentx/tools/__init__.py +26 -0
  258. langchain_agentx/tools/agent/__init__.py +9 -0
  259. langchain_agentx/tools/agent/backend.py +53 -0
  260. langchain_agentx/tools/agent/built_in/__init__.py +19 -0
  261. langchain_agentx/tools/agent/built_in/agentx_guide.py +65 -0
  262. langchain_agentx/tools/agent/built_in/explore.py +80 -0
  263. langchain_agentx/tools/agent/built_in/general.py +57 -0
  264. langchain_agentx/tools/agent/built_in/plan.py +89 -0
  265. langchain_agentx/tools/agent/built_in/statusline_setup.py +64 -0
  266. langchain_agentx/tools/agent/built_in/verification.py +120 -0
  267. langchain_agentx/tools/agent/builtin_subagent_loader.py +89 -0
  268. langchain_agentx/tools/agent/cwd_resolution.py +119 -0
  269. langchain_agentx/tools/agent/limits.py +26 -0
  270. langchain_agentx/tools/agent/loader.py +270 -0
  271. langchain_agentx/tools/agent/models.py +85 -0
  272. langchain_agentx/tools/agent/prompt.py +120 -0
  273. langchain_agentx/tools/agent/registry/__init__.py +18 -0
  274. langchain_agentx/tools/agent/registry/config.py +29 -0
  275. langchain_agentx/tools/agent/registry/registry.py +47 -0
  276. langchain_agentx/tools/agent/scope.py +137 -0
  277. langchain_agentx/tools/agent/tool.py +256 -0
  278. langchain_agentx/tools/bash/__init__.py +9 -0
  279. langchain_agentx/tools/bash/ast_security.py +571 -0
  280. langchain_agentx/tools/bash/backend.py +1447 -0
  281. langchain_agentx/tools/bash/bash_hardening.py +734 -0
  282. langchain_agentx/tools/bash/bash_runtime_contract.py +41 -0
  283. langchain_agentx/tools/bash/cwd_reporter.py +95 -0
  284. langchain_agentx/tools/bash/limits.py +71 -0
  285. langchain_agentx/tools/bash/mode_validation.py +282 -0
  286. langchain_agentx/tools/bash/models.py +131 -0
  287. langchain_agentx/tools/bash/observability.py +148 -0
  288. langchain_agentx/tools/bash/output_utils.py +200 -0
  289. langchain_agentx/tools/bash/path_security.py +2429 -0
  290. langchain_agentx/tools/bash/prompt.py +68 -0
  291. langchain_agentx/tools/bash/read_only_validation.py +589 -0
  292. langchain_agentx/tools/bash/result_presenter.py +324 -0
  293. langchain_agentx/tools/bash/sandbox_decision.py +133 -0
  294. langchain_agentx/tools/bash/security.py +311 -0
  295. langchain_agentx/tools/bash/sed_edit_parser.py +243 -0
  296. langchain_agentx/tools/bash/sed_validation.py +163 -0
  297. langchain_agentx/tools/bash/semantics.py +111 -0
  298. langchain_agentx/tools/bash/session_manager.py +205 -0
  299. langchain_agentx/tools/bash/session_runtime.py +290 -0
  300. langchain_agentx/tools/bash/shell_locator.py +191 -0
  301. langchain_agentx/tools/bash/task_runtime.py +91 -0
  302. langchain_agentx/tools/bash/tool.py +939 -0
  303. langchain_agentx/tools/bash/windows_shell_quoting.py +45 -0
  304. langchain_agentx/tools/glob/__init__.py +9 -0
  305. langchain_agentx/tools/glob/models.py +57 -0
  306. langchain_agentx/tools/glob/pagination.py +30 -0
  307. langchain_agentx/tools/glob/prompt.py +24 -0
  308. langchain_agentx/tools/glob/rg_list_backend.py +139 -0
  309. langchain_agentx/tools/glob/rg_pattern.py +44 -0
  310. langchain_agentx/tools/glob/tool.py +327 -0
  311. langchain_agentx/tools/grep/__init__.py +7 -0
  312. langchain_agentx/tools/grep/backend.py +375 -0
  313. langchain_agentx/tools/grep/models.py +127 -0
  314. langchain_agentx/tools/grep/prompt.py +30 -0
  315. langchain_agentx/tools/grep/rg_subprocess_controller.py +114 -0
  316. langchain_agentx/tools/grep/tool.py +475 -0
  317. langchain_agentx/tools/read/__init__.py +9 -0
  318. langchain_agentx/tools/read/backend.py +415 -0
  319. langchain_agentx/tools/read/limits.py +67 -0
  320. langchain_agentx/tools/read/models.py +156 -0
  321. langchain_agentx/tools/read/prompt.py +73 -0
  322. langchain_agentx/tools/read/tool.py +494 -0
  323. langchain_agentx/tools/ripgrep_plugin_exclusions.py +137 -0
  324. langchain_agentx/tools/skill/__init__.py +4 -0
  325. langchain_agentx/tools/skill/argument_substitution.py +80 -0
  326. langchain_agentx/tools/skill/loader.py +196 -0
  327. langchain_agentx/tools/skill/models.py +88 -0
  328. langchain_agentx/tools/skill/policy.py +80 -0
  329. langchain_agentx/tools/skill/prompt.py +35 -0
  330. langchain_agentx/tools/skill/tool.py +222 -0
  331. langchain_agentx/utils/__init__.py +0 -0
  332. langchain_agentx/utils/cwd.py +124 -0
  333. langchain_agentx/utils/host_platform.py +112 -0
  334. langchain_agentx/utils/path_hierarchy.py +48 -0
  335. langchain_agentx/utils/path_user_input.py +66 -0
  336. langchain_agentx/utils/rg_executable.py +18 -0
  337. langchain_agentx/utils/subprocess_text.py +101 -0
  338. langchain_agentx/utils/temp_paths.py +77 -0
  339. langchain_agentx/utils/unc_path.py +25 -0
  340. langchain_agentx/utils/win_reserved_paths.py +51 -0
  341. langchain_agentx/workflow/__init__.py +7 -0
  342. langchain_agentx/workflow/base.py +97 -0
  343. langchain_agentx/workflow/batch.py +55 -0
  344. langchain_agentx/workflow/dag.py +54 -0
  345. langchain_agentx/workspace/__init__.py +13 -0
  346. langchain_agentx/workspace/config.py +140 -0
  347. langchain_agentx/workspace/path_key_normalizer.py +30 -0
  348. langchain_agentx/workspace/resolver.py +74 -0
  349. langchain_agentx/workspace/validators.py +41 -0
  350. langchain_agentx_python-0.1.dist-info/LICENSE +201 -0
  351. langchain_agentx_python-0.1.dist-info/METADATA +513 -0
  352. langchain_agentx_python-0.1.dist-info/RECORD +354 -0
  353. langchain_agentx_python-0.1.dist-info/WHEEL +5 -0
  354. langchain_agentx_python-0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,211 @@
1
+ """
2
+ plugin/registries.py — Plugin 能力注册表(Skill/Command/Agent)。
3
+
4
+ 职责:
5
+ 管理插件能力索引与按插件来源的批量注销。
6
+
7
+ 链路位置:
8
+ PluginLoader 注册阶段写入,运行阶段由调用方按 key 查找能力。
9
+
10
+ 当前裁剪范围:
11
+ 仅覆盖 Skill/Command/AgentDefinition;不含 MCP 注册管理。
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+
19
+ from .types import ComponentLoadFailedError, LoadedPlugin, PluginError
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class SkillEntry:
24
+ key: str
25
+ plugin_source: str
26
+ path: Path
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class CommandEntry:
31
+ key: str
32
+ plugin_source: str
33
+ name: str
34
+ path: Path
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class AgentDefinitionEntry:
39
+ key: str
40
+ plugin_source: str
41
+ name: str
42
+ path: Path
43
+
44
+
45
+ class SkillRegistry:
46
+ def __init__(self) -> None:
47
+ self._skills: dict[str, SkillEntry] = {}
48
+ self._by_plugin: dict[str, list[str]] = {}
49
+
50
+ def register(self, plugin: LoadedPlugin) -> list[PluginError]:
51
+ if plugin.skills_dir is None or not plugin.skills_dir.exists():
52
+ self._by_plugin[plugin.source] = []
53
+ return []
54
+
55
+ errors: list[PluginError] = []
56
+ registered: list[str] = []
57
+ for skill_dir in sorted(plugin.skills_dir.iterdir()):
58
+ if not skill_dir.is_dir():
59
+ continue
60
+ skill_md = skill_dir / "SKILL.md"
61
+ if not skill_md.exists():
62
+ continue
63
+ skill_name = skill_dir.name
64
+ key = f"{plugin.source}:{skill_name}"
65
+ if key in self._skills:
66
+ errors.append(
67
+ ComponentLoadFailedError(
68
+ source=plugin.source,
69
+ plugin=plugin.name,
70
+ component="skills",
71
+ path=str(skill_md),
72
+ reason=f"duplicate skill key: {key}",
73
+ )
74
+ )
75
+ continue
76
+ self._skills[key] = SkillEntry(key=key, plugin_source=plugin.source, path=skill_md)
77
+ registered.append(key)
78
+ self._by_plugin[plugin.source] = registered
79
+ return errors
80
+
81
+ def get(self, key: str) -> SkillEntry | None:
82
+ if key in self._skills:
83
+ return self._skills[key]
84
+ return self._find_by_short_name(key)
85
+
86
+ def unregister_plugin(self, plugin_source: str) -> None:
87
+ for key in self._by_plugin.pop(plugin_source, []):
88
+ self._skills.pop(key, None)
89
+
90
+ def list_all(self) -> list[SkillEntry]:
91
+ return list(self._skills.values())
92
+
93
+ def _find_by_short_name(self, short_name: str) -> SkillEntry | None:
94
+ matches = [entry for entry in self._skills.values() if entry.key.endswith(f":{short_name}")]
95
+ return matches[0] if len(matches) == 1 else None
96
+
97
+
98
+ class CommandRegistry:
99
+ def __init__(self) -> None:
100
+ self._commands: dict[str, CommandEntry] = {}
101
+ self._by_plugin: dict[str, list[str]] = {}
102
+
103
+ def register(self, plugin: LoadedPlugin) -> list[PluginError]:
104
+ if plugin.commands_dir is None or not plugin.commands_dir.exists():
105
+ self._by_plugin[plugin.source] = []
106
+ return []
107
+
108
+ errors: list[PluginError] = []
109
+ registered: list[str] = []
110
+ for cmd_file in sorted(plugin.commands_dir.glob("*.md")):
111
+ cmd_name = cmd_file.stem
112
+ key = f"{plugin.source}:{cmd_name}"
113
+ if key in self._commands:
114
+ errors.append(
115
+ ComponentLoadFailedError(
116
+ source=plugin.source,
117
+ plugin=plugin.name,
118
+ component="commands",
119
+ path=str(cmd_file),
120
+ reason=f"duplicate command key: {key}",
121
+ )
122
+ )
123
+ continue
124
+ self._commands[key] = CommandEntry(
125
+ key=key,
126
+ plugin_source=plugin.source,
127
+ name=cmd_name,
128
+ path=cmd_file,
129
+ )
130
+ registered.append(key)
131
+ self._by_plugin[plugin.source] = registered
132
+ return errors
133
+
134
+ def get(self, name: str) -> CommandEntry | None:
135
+ if name in self._commands:
136
+ return self._commands[name]
137
+ return self._find_by_short_name(name)
138
+
139
+ def unregister_plugin(self, plugin_source: str) -> None:
140
+ for key in self._by_plugin.pop(plugin_source, []):
141
+ self._commands.pop(key, None)
142
+
143
+ def list_all(self) -> list[CommandEntry]:
144
+ return list(self._commands.values())
145
+
146
+ def _find_by_short_name(self, short_name: str) -> CommandEntry | None:
147
+ matches = [entry for entry in self._commands.values() if entry.key.endswith(f":{short_name}")]
148
+ return matches[0] if len(matches) == 1 else None
149
+
150
+
151
+ class AgentRegistry:
152
+ def __init__(self) -> None:
153
+ self._agents: dict[str, AgentDefinitionEntry] = {}
154
+ self._by_plugin: dict[str, list[str]] = {}
155
+
156
+ def register(self, plugin: LoadedPlugin) -> list[PluginError]:
157
+ if plugin.agents_dir is None or not plugin.agents_dir.exists():
158
+ self._by_plugin[plugin.source] = []
159
+ return []
160
+
161
+ errors: list[PluginError] = []
162
+ registered: list[str] = []
163
+ for agent_file in sorted(plugin.agents_dir.glob("*.md")):
164
+ agent_name = agent_file.stem
165
+ key = f"{plugin.source}:{agent_name}"
166
+ if key in self._agents:
167
+ errors.append(
168
+ ComponentLoadFailedError(
169
+ source=plugin.source,
170
+ plugin=plugin.name,
171
+ component="agents",
172
+ path=str(agent_file),
173
+ reason=f"duplicate agent key: {key}",
174
+ )
175
+ )
176
+ continue
177
+ self._agents[key] = AgentDefinitionEntry(
178
+ key=key,
179
+ plugin_source=plugin.source,
180
+ name=agent_name,
181
+ path=agent_file,
182
+ )
183
+ registered.append(key)
184
+ self._by_plugin[plugin.source] = registered
185
+ return errors
186
+
187
+ def get(self, name: str) -> AgentDefinitionEntry | None:
188
+ if name in self._agents:
189
+ return self._agents[name]
190
+ return self._find_by_short_name(name)
191
+
192
+ def unregister_plugin(self, plugin_source: str) -> None:
193
+ for key in self._by_plugin.pop(plugin_source, []):
194
+ self._agents.pop(key, None)
195
+
196
+ def list_all(self) -> list[AgentDefinitionEntry]:
197
+ return list(self._agents.values())
198
+
199
+ def _find_by_short_name(self, short_name: str) -> AgentDefinitionEntry | None:
200
+ matches = [entry for entry in self._agents.values() if entry.key.endswith(f":{short_name}")]
201
+ return matches[0] if len(matches) == 1 else None
202
+
203
+
204
+ __all__ = [
205
+ "AgentDefinitionEntry",
206
+ "AgentRegistry",
207
+ "CommandEntry",
208
+ "CommandRegistry",
209
+ "SkillEntry",
210
+ "SkillRegistry",
211
+ ]
@@ -0,0 +1,142 @@
1
+ """
2
+ plugin/types.py — Plugin 运行时数据类型定义。
3
+
4
+ 职责:
5
+ 定义 LoadedPlugin、PluginError 判别联合与 PluginLoadResult。
6
+
7
+ 链路位置:
8
+ 被 PluginLoader、各 Registry 与容器集成层共享。
9
+
10
+ 当前裁剪范围:
11
+ 仅覆盖 P0/P1 数据类型,不含 MCP/依赖解析相关专用错误类型。
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from dataclasses import dataclass, field
17
+ from pathlib import Path
18
+ from typing import Any, Literal
19
+
20
+
21
+ @dataclass
22
+ class LoadedPlugin:
23
+ """PluginLoader 加载后的运行时表示。"""
24
+
25
+ name: str
26
+ description: str
27
+ version: str
28
+ path: str
29
+ source: str
30
+ enabled: bool = True
31
+ is_builtin: bool = False
32
+ skills_dir: Path | None = None
33
+ commands_dir: Path | None = None
34
+ agents_dir: Path | None = None
35
+ hooks_config: dict[str, Any] | None = None
36
+ mcp_servers: dict[str, dict[str, Any]] = field(default_factory=dict)
37
+ dependencies: list[str] = field(default_factory=list)
38
+ applicable_containers: list[str] = field(default_factory=list)
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class ManifestParseError:
43
+ type: Literal["manifest-parse-error"] = "manifest-parse-error"
44
+ source: str = ""
45
+ manifest_path: str = ""
46
+ parse_error: str = ""
47
+
48
+
49
+ @dataclass(frozen=True)
50
+ class ManifestValidationError:
51
+ type: Literal["manifest-validation-error"] = "manifest-validation-error"
52
+ source: str = ""
53
+ manifest_path: str = ""
54
+ validation_errors: list[str] = field(default_factory=list)
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class ComponentLoadFailedError:
59
+ type: Literal["component-load-failed"] = "component-load-failed"
60
+ source: str = ""
61
+ plugin: str = ""
62
+ component: str = ""
63
+ path: str = ""
64
+ reason: str = ""
65
+
66
+
67
+ @dataclass(frozen=True)
68
+ class HookLoadFailedError:
69
+ type: Literal["hook-load-failed"] = "hook-load-failed"
70
+ source: str = ""
71
+ plugin: str = ""
72
+ hook_path: str = ""
73
+ reason: str = ""
74
+
75
+
76
+ @dataclass(frozen=True)
77
+ class GenericPluginError:
78
+ type: Literal["generic-error"] = "generic-error"
79
+ source: str = ""
80
+ plugin: str = ""
81
+ error: str = ""
82
+
83
+
84
+ PluginError = (
85
+ ManifestParseError
86
+ | ManifestValidationError
87
+ | ComponentLoadFailedError
88
+ | HookLoadFailedError
89
+ | GenericPluginError
90
+ )
91
+
92
+
93
+ def get_plugin_error_message(error: PluginError) -> str:
94
+ """将 PluginError 转换为可日志化消息。"""
95
+ if isinstance(error, ManifestParseError):
96
+ return (
97
+ f"[{error.source}] manifest parse error at {error.manifest_path}: "
98
+ f"{error.parse_error}"
99
+ )
100
+ if isinstance(error, ManifestValidationError):
101
+ details = ", ".join(error.validation_errors)
102
+ return (
103
+ f"[{error.source}] manifest validation error at {error.manifest_path}: "
104
+ f"{details}"
105
+ )
106
+ if isinstance(error, ComponentLoadFailedError):
107
+ return (
108
+ f"[{error.source}] {error.component} load failed at {error.path}: "
109
+ f"{error.reason}"
110
+ )
111
+ if isinstance(error, HookLoadFailedError):
112
+ return f"[{error.source}] hook load failed at {error.hook_path}: {error.reason}"
113
+ return f"[{error.source}] plugin error: {error.error}"
114
+
115
+
116
+ @dataclass
117
+ class PluginLoadResult:
118
+ """Plugin 加载结果。"""
119
+
120
+ enabled: list[LoadedPlugin]
121
+ errors: list[PluginError]
122
+
123
+ @property
124
+ def has_errors(self) -> bool:
125
+ return bool(self.errors)
126
+
127
+ def log_errors(self, logger: Any) -> None:
128
+ for error in self.errors:
129
+ logger.warning("plugin load error: %s", get_plugin_error_message(error))
130
+
131
+
132
+ __all__ = [
133
+ "ComponentLoadFailedError",
134
+ "GenericPluginError",
135
+ "HookLoadFailedError",
136
+ "LoadedPlugin",
137
+ "ManifestParseError",
138
+ "ManifestValidationError",
139
+ "PluginError",
140
+ "PluginLoadResult",
141
+ "get_plugin_error_message",
142
+ ]
@@ -0,0 +1,27 @@
1
+ """
2
+ Provider layer for langchain_agentx.
3
+
4
+ 职责:
5
+ 对外提供模型 Provider 兼容封装入口。
6
+
7
+ 链路位置:
8
+ 被 examples 与 loop/integration 侧导入,用于构建可替换的 ChatModel。
9
+
10
+ 当前裁剪范围:
11
+ - CompatibleChatOpenAI(OpenAI 兼容 reasoning_content 透传)
12
+ - get_chat_openai / get_chat_anthropic(从环境变量构造 ChatModel 的可复用组合件)
13
+ """
14
+
15
+ from .anthropic import get_chat_anthropic
16
+ from .compatible_chat_openai import CompatibleChatOpenAI
17
+ from .model_profile import ModelProfile, ModelProfileRegistry
18
+ from .openai import get_chat_openai
19
+
20
+ __all__ = [
21
+ "CompatibleChatOpenAI",
22
+ "get_chat_anthropic",
23
+ "get_chat_openai",
24
+ "ModelProfile",
25
+ "ModelProfileRegistry",
26
+ ]
27
+
@@ -0,0 +1,121 @@
1
+ """provider/anthropic.py — Anthropic ChatModel 工厂(可复用组合件)。
2
+
3
+ 职责:
4
+ - 从环境变量读取 Anthropic 配置(auth token/base_url/model)。
5
+ - 处理部分企业代理环境下 httpx 对 socks:// 的不兼容。
6
+ - 组装并返回 ChatAnthropic。
7
+
8
+ 约束:
9
+ - 不加载 .env;由调用方(例如 examples)负责把 .env 注入到环境变量。
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ from typing import Any
16
+
17
+ from langchain_core.language_models.chat_models import BaseChatModel
18
+
19
+ from .env import get_env_first, parse_positive_int
20
+ from .model_profile import ModelProfileRegistry
21
+
22
+
23
+ def _sanitize_proxy_env_for_anthropic() -> None:
24
+ """Anthropic/httpx 不接受 socks://,保留 http/https 代理并移除异常 all_proxy。"""
25
+ for key in ("all_proxy", "ALL_PROXY"):
26
+ value = os.environ.get(key)
27
+ if not value:
28
+ continue
29
+ if value.strip().lower().startswith("socks://"):
30
+ os.environ.pop(key, None)
31
+
32
+
33
+ def _resolve_claude_max_tokens(explicit: int | None, *, profile_default: int) -> int:
34
+ if explicit is not None:
35
+ return int(explicit)
36
+ override = get_env_first("CLAUDE_MAX_OUTPUT_TOKENS", "CLAUDE_CODE_MAX_OUTPUT_TOKENS")
37
+ parsed = parse_positive_int(override)
38
+ return parsed if parsed is not None else profile_default
39
+
40
+
41
+ def _is_thinking_enabled(thinking_option: Any) -> bool:
42
+ if isinstance(thinking_option, bool):
43
+ return thinking_option
44
+ if isinstance(thinking_option, dict):
45
+ enabled = thinking_option.get("enabled")
46
+ if isinstance(enabled, bool):
47
+ return enabled
48
+ return bool(thinking_option)
49
+ return False
50
+
51
+
52
+ def get_chat_anthropic(
53
+ *,
54
+ model: str | None = None,
55
+ api_key: str | None = None,
56
+ base_url: str | None = None,
57
+ temperature: float | None = None,
58
+ max_tokens: int | None = None,
59
+ top_p: float | None = None,
60
+ streaming: bool = True,
61
+ model_profile_registry: ModelProfileRegistry | None = None,
62
+ **kwargs: Any,
63
+ ) -> BaseChatModel:
64
+ from langchain_anthropic import ChatAnthropic # pyright: ignore[reportMissingImports]
65
+
66
+ _sanitize_proxy_env_for_anthropic()
67
+
68
+ resolved_api_key = api_key or get_env_first(
69
+ "LANGCHAIN_AGENTX_ANTHROPIC_AUTH_TOKEN",
70
+ "ANTHROPIC_AUTH_TOKEN",
71
+ "ANTHROPIC_API_KEY",
72
+ )
73
+ resolved_base_url = base_url or get_env_first(
74
+ "LANGCHAIN_AGENTX_ANTHROPIC_BASE_URL",
75
+ "ANTHROPIC_BASE_URL",
76
+ )
77
+ resolved_model = (
78
+ model
79
+ or get_env_first("LANGCHAIN_AGENTX_ANTHROPIC_MODEL", "ANTHROPIC_MODEL")
80
+ or "claude-sonnet-4-20250514"
81
+ )
82
+ profile = (
83
+ model_profile_registry.get(resolved_model)
84
+ if model_profile_registry is not None
85
+ else ModelProfileRegistry().defaults
86
+ )
87
+
88
+ if not resolved_api_key or not resolved_base_url:
89
+ raise RuntimeError(
90
+ "缺少 Anthropic 配置:请设置 ANTHROPIC_AUTH_TOKEN(或 ANTHROPIC_API_KEY /"
91
+ " LANGCHAIN_AGENTX_ANTHROPIC_AUTH_TOKEN)以及 ANTHROPIC_BASE_URL"
92
+ "(或 LANGCHAIN_AGENTX_ANTHROPIC_BASE_URL)。默认模型名可通过"
93
+ " ANTHROPIC_MODEL / LANGCHAIN_AGENTX_ANTHROPIC_MODEL 或 model 参数指定。"
94
+ )
95
+
96
+ resolved_max_tokens = _resolve_claude_max_tokens(
97
+ max_tokens,
98
+ profile_default=profile.max_output_tokens,
99
+ )
100
+ thinking_enabled = _is_thinking_enabled(kwargs.get("thinking"))
101
+ resolved_temperature = profile.default_temperature if temperature is None else float(temperature)
102
+ resolved_top_p = profile.default_top_p if top_p is None else float(top_p)
103
+
104
+ anthropic_kwargs: dict[str, Any] = {
105
+ "model": resolved_model,
106
+ "anthropic_api_key": resolved_api_key,
107
+ "anthropic_api_url": resolved_base_url.rstrip("/"),
108
+ "max_tokens": resolved_max_tokens,
109
+ "streaming": streaming,
110
+ **kwargs,
111
+ }
112
+ if resolved_top_p is not None:
113
+ anthropic_kwargs["top_p"] = resolved_top_p
114
+ if not thinking_enabled:
115
+ anthropic_kwargs["temperature"] = resolved_temperature
116
+
117
+ return ChatAnthropic(**anthropic_kwargs)
118
+
119
+
120
+ __all__ = ["get_chat_anthropic"]
121
+
@@ -0,0 +1,86 @@
1
+ """
2
+ CompatibleChatOpenAI
3
+ ====================
4
+
5
+ 为 **任意 OpenAI 兼容聊天服务** 提供一个轻量级的 LangChain ChatModel 封装,
6
+ 在不 fork `langchain-openai` 的前提下:
7
+
8
+ - 复用官方 `ChatOpenAI` 的绝大部分行为(重试、流式、usage 统计等)
9
+ - 额外把流式响应中的 `reasoning_content` 注入到
10
+ `AIMessageChunk.additional_kwargs["reasoning_content"]`,让下游组件按
11
+ LangChain v2 标准路径解析 reasoning。
12
+ - 在组请求体时把历史 ``AIMessage.additional_kwargs["reasoning_content"]`` 写回
13
+ ``messages[].reasoning_content``(``langchain_openai`` 默认不落该字段)。
14
+ 部分 OpenAI 兼容思考模型要求多轮必须把上一轮 reasoning 原样带回,否则会 400。
15
+
16
+ 与 loop 终局契约:
17
+ 若 HTTP/鉴权等错误在 invoke/ainvoke 阶段以异常抛出,
18
+ 由 model_nodes 统一合成带 response_metadata.langchain_agentx_api_error/error 的 AIMessage。
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from typing import Any, Dict, Type
24
+
25
+ from langchain_core.language_models import LanguageModelInput
26
+ from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessageChunk
27
+ from langchain_core.outputs import ChatGenerationChunk
28
+ from langchain_openai import ChatOpenAI
29
+
30
+
31
+ class CompatibleChatOpenAI(ChatOpenAI):
32
+ """OpenAI 兼容 ChatModel 封装(支持 reasoning_content)。"""
33
+
34
+ def _get_request_payload(
35
+ self,
36
+ input_: LanguageModelInput,
37
+ *,
38
+ stop: list[str] | None = None,
39
+ **kwargs: Any,
40
+ ) -> dict[str, Any]:
41
+ """与基类一致,并为 chat/completions 的 assistant 历史补上 ``reasoning_content``。"""
42
+ lc_messages = self._convert_input(input_).to_messages()
43
+ payload = super()._get_request_payload(input_, stop=stop, **kwargs)
44
+ serialized = payload.get("messages")
45
+ if not isinstance(serialized, list) or len(serialized) != len(lc_messages):
46
+ return payload
47
+ for msg_dict, lc_msg in zip(serialized, lc_messages):
48
+ if not isinstance(lc_msg, AIMessage):
49
+ continue
50
+ rc = lc_msg.additional_kwargs.get("reasoning_content")
51
+ if isinstance(rc, str) and rc:
52
+ msg_dict["reasoning_content"] = rc
53
+ return payload
54
+
55
+ def _convert_chunk_to_generation_chunk(
56
+ self,
57
+ chunk: Dict[str, Any],
58
+ default_chunk_class: Type[BaseMessageChunk],
59
+ base_generation_info: Dict[str, Any] | None,
60
+ ) -> ChatGenerationChunk | None:
61
+ gen_chunk = super()._convert_chunk_to_generation_chunk(
62
+ chunk, default_chunk_class, base_generation_info
63
+ )
64
+ if gen_chunk is None:
65
+ return None
66
+
67
+ message = gen_chunk.message
68
+ if not isinstance(message, AIMessageChunk):
69
+ return gen_chunk
70
+
71
+ choices = chunk.get("choices", []) or chunk.get("chunk", {}).get("choices", [])
72
+ if not choices:
73
+ return gen_chunk
74
+
75
+ delta = choices[0].get("delta") or {}
76
+ rc = delta.get("reasoning_content")
77
+ if isinstance(rc, str) and rc:
78
+ ak = getattr(message, "additional_kwargs", None) or {}
79
+ ak["reasoning_content"] = rc
80
+ message.additional_kwargs = ak
81
+
82
+ return gen_chunk
83
+
84
+
85
+ __all__ = ["CompatibleChatOpenAI"]
86
+
@@ -0,0 +1,45 @@
1
+ """provider/env.py — provider 工厂的环境变量解析工具。
2
+
3
+ 职责:
4
+ 为 provider 工厂函数提供一致的 env 读取与布尔/数值解析。
5
+
6
+ 约束:
7
+ - 只读取 os.environ,不加载 .env(示例层负责)
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+
14
+
15
+ def get_env_first(*names: str) -> str | None:
16
+ for name in names:
17
+ v = os.environ.get(name)
18
+ if v and str(v).strip():
19
+ return str(v).strip()
20
+ return None
21
+
22
+
23
+ def parse_positive_int(raw: str | None) -> int | None:
24
+ if raw is None:
25
+ return None
26
+ try:
27
+ value = int(str(raw).strip())
28
+ except ValueError:
29
+ return None
30
+ return value if value > 0 else None
31
+
32
+
33
+ def parse_bool(raw: str | None) -> bool | None:
34
+ if raw is None:
35
+ return None
36
+ v = str(raw).strip().lower()
37
+ if v in {"1", "true", "yes", "y", "on"}:
38
+ return True
39
+ if v in {"0", "false", "no", "n", "off"}:
40
+ return False
41
+ return None
42
+
43
+
44
+ __all__ = ["get_env_first", "parse_bool", "parse_positive_int"]
45
+