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,73 @@
1
+ """
2
+ tools/read/prompt.py — ReadRuntimeTool 工具名与 prompt 模板
3
+
4
+ 职责:
5
+ 集中管理工具名常量、面向模型的 description 和 prompt 模板文本。
6
+ 纯字符串/常量,无 I/O,无框架依赖,可单独测试。
7
+
8
+ 对应 CC prompt.ts,包含:
9
+ FILE_READ_TOOL_NAME → TOOL_NAME
10
+ DESCRIPTION → DESCRIPTION
11
+ FILE_UNCHANGED_STUB → FILE_UNCHANGED_STUB
12
+ renderPromptTemplate → get_prompt()
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ TOOL_NAME = "read_file"
18
+ """工具名,面向模型暴露,唯一标识。"""
19
+
20
+ DESCRIPTION = "Read a file from the local filesystem."
21
+ """简短描述,用于 bind_tools 阶段工具列表展示(对应 CC DESCRIPTION)。"""
22
+
23
+ FILE_UNCHANGED_STUB = (
24
+ "File unchanged since last read. "
25
+ "The content from the earlier read result in this conversation is still current "
26
+ "— refer to that instead of re-reading."
27
+ )
28
+ """
29
+ 重复读取去重时返回的 stub 文本(对应 CC FILE_UNCHANGED_STUB)。
30
+ model 收到此文本后应直接引用历史上下文,而非重新读取。
31
+ """
32
+
33
+ MAX_LINES_TO_READ = 2000
34
+ """面向模型 prompt 中说明的默认行数上限(与 limits.py 同步)。"""
35
+
36
+
37
+ def get_prompt() -> str:
38
+ """
39
+ 返回面向模型的完整使用说明(对应 CC renderPromptTemplate())。
40
+
41
+ 动态引用 limits 配置,确保 prompt 中展示的限制值与实际执行一致。
42
+ """
43
+ from .limits import get_read_limits
44
+
45
+ limits = get_read_limits()
46
+ max_size_kb = limits["max_size_bytes"] // 1024
47
+ pdf_max_pages = limits["pdf_max_pages"]
48
+
49
+ return (
50
+ f"Reads a file from the local filesystem.\n"
51
+ f"\n"
52
+ f"Usage:\n"
53
+ f"- file_path must be an absolute path\n"
54
+ f"- By default reads up to {MAX_LINES_TO_READ} lines from the start of the file\n"
55
+ f"- Files larger than {max_size_kb}KB will return an error; "
56
+ f"use offset and limit to read specific portions\n"
57
+ f"- You can optionally specify offset and limit for large files; "
58
+ f"it is recommended to read the whole file by not providing these parameters\n"
59
+ f"- Results are returned with line numbers starting at 1 (cat -n format: "
60
+ f'" 1\\t<line content>")\n'
61
+ f"- If a file exists but is shorter than the provided offset, a warning is returned\n"
62
+ f"- If a file exists but has empty contents, a warning is returned\n"
63
+ f"\n"
64
+ f"Supported file types beyond plain text:\n"
65
+ f"- Images (PNG, JPG, JPEG, GIF, WEBP): contents are returned as base64-encoded data\n"
66
+ f"- PDF files (.pdf): text is extracted page by page; "
67
+ f"use the pages parameter for large PDFs (e.g. pages=\"1-5\", max {pdf_max_pages} pages per request)\n"
68
+ f"- Jupyter notebooks (.ipynb): returns all cells with their outputs\n"
69
+ f"\n"
70
+ f"Restrictions:\n"
71
+ f"- This tool can only read files, not directories\n"
72
+ f"- Binary files (non-image, non-PDF) cannot be read; use appropriate tools instead\n"
73
+ )
@@ -0,0 +1,494 @@
1
+ """
2
+ tools/read/tool.py — ReadRuntimeTool 主类
3
+
4
+ 职责:
5
+ 实现 RuntimeTool 的所有生命周期 hook,编排文件读取完整流程。
6
+ 核心业务 I/O 委托给 FileBackend,描述文本来自 prompt.py,
7
+ 数值配置来自 limits.py,输入/输出模型来自 models.py。
8
+
9
+ 对应 CC FileReadTool.ts 主体部分。
10
+ present() 对应 CC UI.tsx 的 renderToolResultMessage()。
11
+ validate_input:Windows 保留名(utils.win_reserved_paths);UNC 与 // 前缀跳过本地 exists/stat(utils.unc_path,与 Grep/Glob 一致)。
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import difflib
17
+ import os
18
+ import re
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ from langchain_agentx.tool_runtime.base import RuntimeTool
23
+ from langchain_agentx.tool_runtime.models import (
24
+ AuthorizationDecision,
25
+ ToolExecutionContext,
26
+ ToolHint,
27
+ ToolResultEnvelope,
28
+ ValidationResult,
29
+ )
30
+ from langchain_agentx.utils.path_user_input import UserPathInputNormalizer
31
+ from langchain_agentx.utils.unc_path import is_unc_path_skip_local_stat
32
+ from langchain_agentx.utils.win_reserved_paths import path_uses_windows_reserved_name
33
+
34
+ from .backend import BINARY_EXTENSIONS, SUPPORTED_IMAGE_EXTS, FileBackend
35
+ from .limits import PDF_MAX_PAGES_PER_READ, get_read_limits
36
+ from .models import (
37
+ FileUnchangedOutput,
38
+ ImageFileOutput,
39
+ NotebookFileOutput,
40
+ PDFFileOutput,
41
+ ReadToolInput,
42
+ TextFileOutput,
43
+ )
44
+
45
+ from .prompt import DESCRIPTION, FILE_UNCHANGED_STUB, TOOL_NAME, get_prompt
46
+
47
+
48
+ class ReadRuntimeTool(RuntimeTool):
49
+ """
50
+ 文件读取工具,支持文本、图片、PDF、Jupyter Notebook。
51
+
52
+ 对应 CC FileReadTool,是工具体系中最高频使用的工具。
53
+
54
+ 特性:
55
+ is_read_only=True 只读,走 read_roots 权限检查
56
+ is_concurrency_safe=True 只读,允许并发执行
57
+ never_truncate=True 自带 limit 分页,禁止 ToolOutputManager 截断
58
+ """
59
+
60
+ name: str = TOOL_NAME
61
+ description: str = DESCRIPTION
62
+ input_model = ReadToolInput
63
+ is_read_only: bool = True
64
+ is_concurrency_safe: bool = True
65
+ never_truncate: bool = True
66
+ dedupe_identical_calls: bool = True
67
+
68
+ # 阻断的设备文件路径(对应 CC BLOCKED_DEVICE_PATHS)
69
+ _BLOCKED_DEVICE_PATHS: frozenset[str] = frozenset({
70
+ "/dev/zero", "/dev/random", "/dev/urandom", "/dev/full",
71
+ "/dev/stdin", "/dev/tty", "/dev/console",
72
+ "/dev/stdout", "/dev/stderr",
73
+ "/dev/fd/0", "/dev/fd/1", "/dev/fd/2",
74
+ })
75
+
76
+ def __init__(
77
+ self,
78
+ *,
79
+ policy: Any | None = None,
80
+ state_bridge: Any | None = None,
81
+ backend: FileBackend | None = None,
82
+ path_normalizer: UserPathInputNormalizer | None = None,
83
+ ) -> None:
84
+ super().__init__(policy=policy, state_bridge=state_bridge)
85
+ self._backend = backend or FileBackend()
86
+ self._path_normalizer = path_normalizer or UserPathInputNormalizer()
87
+
88
+ # ------------------------------------------------------------------
89
+ # Hook 1: normalize_input
90
+ # ------------------------------------------------------------------
91
+
92
+ def normalize_input(
93
+ self, raw: dict[str, Any], ctx: ToolExecutionContext
94
+ ) -> dict[str, Any]:
95
+ """
96
+ 路径规范化:展开 ~ 和 ..,转为绝对路径。
97
+ limit 上限收紧:不超过 max_lines。
98
+ 对应 CC backfillObservableInput() + expandPath()。
99
+ """
100
+ raw = dict(raw) # 不修改原始 dict
101
+
102
+ path = raw.get("file_path", "")
103
+ if isinstance(path, str) and path:
104
+ raw["file_path"] = self._path_normalizer.normalize_and_expand(
105
+ path, base_dir=ctx.cwd
106
+ )
107
+
108
+ limits = get_read_limits()
109
+ if raw.get("limit") is not None:
110
+ raw["limit"] = min(int(raw["limit"]), limits["max_lines"])
111
+
112
+ return raw
113
+
114
+ # ------------------------------------------------------------------
115
+ # Hook 3: validate_input
116
+ # ------------------------------------------------------------------
117
+
118
+ def validate_input(
119
+ self, data: dict[str, Any], ctx: ToolExecutionContext
120
+ ) -> ValidationResult:
121
+ """
122
+ 语义校验,执行顺序与 CC validateInput() 保持一致:
123
+ 先无 I/O 检查(快速 fail)→ Windows 保留名(仅 win32)→ UNC 短路 → 再 I/O 检查。
124
+
125
+ 对应 CC FileReadTool.validateInput()(含 UNC);保留名为本工程 Windows 补强。
126
+ """
127
+ inp = ReadToolInput.model_validate(data)
128
+ path = inp.file_path
129
+ ext = Path(path).suffix.lower().lstrip(".")
130
+
131
+ # ---- [1] pages 参数格式校验(纯字符串,无 I/O)----
132
+ if inp.pages is not None:
133
+ if ext != "pdf":
134
+ return ValidationResult(
135
+ ok=False,
136
+ message="pages parameter is only valid for PDF files.",
137
+ )
138
+ m = re.fullmatch(r"(\d+)(?:-(\d+))?", inp.pages.strip())
139
+ if not m:
140
+ return ValidationResult(
141
+ ok=False,
142
+ message=(
143
+ f'Invalid pages format: "{inp.pages}". '
144
+ 'Use formats like "1-5", "3", or "10-20".'
145
+ ),
146
+ )
147
+ start = int(m.group(1))
148
+ end = int(m.group(2)) if m.group(2) else start
149
+ if end - start + 1 > PDF_MAX_PAGES_PER_READ:
150
+ return ValidationResult(
151
+ ok=False,
152
+ message=(
153
+ f'Page range "{inp.pages}" exceeds maximum of '
154
+ f"{PDF_MAX_PAGES_PER_READ} pages per request."
155
+ ),
156
+ )
157
+
158
+ # ---- [2] 设备文件阻断(路径字符串检查,无 I/O)----
159
+ if path in self._BLOCKED_DEVICE_PATHS or self._is_proc_stdio(path):
160
+ return ValidationResult(
161
+ ok=False,
162
+ message=(
163
+ f"Cannot read '{path}': "
164
+ "this device file would block or produce infinite output."
165
+ ),
166
+ )
167
+
168
+ # ---- [3] Windows 保留设备名(路径段 stem,无 I/O;在 UNC 短路之前)----
169
+ if path_uses_windows_reserved_name(path):
170
+ return ValidationResult(
171
+ ok=False,
172
+ message=(
173
+ f"Cannot read '{path}': uses a Windows reserved device name "
174
+ "(CON, PRN, AUX, NUL, COM1-9, LPT1-9). "
175
+ "These paths can hang or behave unpredictably."
176
+ ),
177
+ )
178
+
179
+ # ---- [4] UNC / SMB 前缀:与 CC 一致,跳过本地 exists/stat(防 NTLM 等)----
180
+ if is_unc_path_skip_local_stat(path):
181
+ return ValidationResult(ok=True)
182
+
183
+ # ---- [5] 二进制扩展名拒绝(字符串检查,无 I/O),PDF/图片例外 ----
184
+ if ext in BINARY_EXTENSIONS and ext not in SUPPORTED_IMAGE_EXTS and ext != "pdf":
185
+ return ValidationResult(
186
+ ok=False,
187
+ message=(
188
+ f"Cannot read binary .{ext} files. "
189
+ "Use appropriate tools for binary file analysis."
190
+ ),
191
+ )
192
+
193
+ # ---- [6] 文件存在性检查(I/O)----
194
+ if not os.path.exists(path):
195
+ similar = self._find_similar_file(path)
196
+ hint = f" Did you mean {similar}?" if similar else ""
197
+ return ValidationResult(
198
+ ok=False,
199
+ message=f"File does not exist: {path}.{hint}",
200
+ )
201
+
202
+ # ---- [7] 目录检查(I/O)----
203
+ if os.path.isdir(path):
204
+ return ValidationResult(
205
+ ok=False,
206
+ message=(
207
+ f"Path is a directory: {path}. "
208
+ "Use the bash tool with ls to list directory contents."
209
+ ),
210
+ )
211
+
212
+ # ---- [8] 文件大小检查(I/O,stat)——图片有独立上限 ----
213
+ limits = get_read_limits()
214
+ if ext in SUPPORTED_IMAGE_EXTS:
215
+ size = os.path.getsize(path)
216
+ if size > limits["image_max_bytes"]:
217
+ return ValidationResult(
218
+ ok=False,
219
+ message=(
220
+ f"Image file is too large ({size:,} bytes, "
221
+ f"max {limits['image_max_bytes']:,} bytes)."
222
+ ),
223
+ )
224
+ elif ext not in {"pdf", "ipynb"}:
225
+ size = os.path.getsize(path)
226
+ if size > limits["max_size_bytes"]:
227
+ return ValidationResult(
228
+ ok=False,
229
+ message=(
230
+ f"File too large ({size:,} bytes, "
231
+ f"max {limits['max_size_bytes']:,} bytes). "
232
+ "Use offset and limit to read specific portions."
233
+ ),
234
+ )
235
+
236
+ return ValidationResult(ok=True)
237
+
238
+ # ------------------------------------------------------------------
239
+ # Hook 4: check_permissions
240
+ # ------------------------------------------------------------------
241
+
242
+ def check_permissions(
243
+ self, data: dict[str, Any], ctx: ToolExecutionContext
244
+ ) -> AuthorizationDecision:
245
+ """委托给注入的 PolicyEngine(走 read_roots 检查)。"""
246
+ return super().check_permissions(data, ctx)
247
+
248
+ # ------------------------------------------------------------------
249
+ # Core: invoke
250
+ # ------------------------------------------------------------------
251
+
252
+ def invoke(
253
+ self, data: dict[str, Any], ctx: ToolExecutionContext
254
+ ) -> Any:
255
+ """
256
+ 核心执行:先检查 dedup(mtime 未变则返回 stub),再调 FileBackend。
257
+ 对应 CC FileReadTool.call() 主逻辑。
258
+ """
259
+ inp = ReadToolInput.model_validate(data)
260
+ path = inp.file_path
261
+ limits = get_read_limits()
262
+
263
+ # dedup 检查:同文件同范围且 mtime 未变,直接返回 stub
264
+ if self._state_bridge is not None:
265
+ last = self._state_bridge.get_last_read(path)
266
+ if (
267
+ last is not None
268
+ and last.get("offset") == inp.offset
269
+ and last.get("limit") == inp.limit
270
+ ):
271
+ try:
272
+ mtime_ms = int(os.path.getmtime(path) * 1000)
273
+ if mtime_ms == last.get("mtime_ms"):
274
+ return FileUnchangedOutput(
275
+ type="file_unchanged", file_path=path
276
+ )
277
+ except OSError:
278
+ pass
279
+
280
+ return self._backend.read(path, inp, limits)
281
+
282
+ # ------------------------------------------------------------------
283
+ # Hook 7: after_invoke
284
+ # ------------------------------------------------------------------
285
+
286
+ def after_invoke(
287
+ self, data: dict[str, Any], result: Any, ctx: ToolExecutionContext
288
+ ) -> None:
289
+ """
290
+ 更新 state_bridge 的读取记录:
291
+ - 供 write/edit 工具的 read-before-write 检查使用
292
+ - 供下次 invoke 的 dedup 检查使用
293
+
294
+ file_unchanged 结果不更新(mtime 没变,记录仍有效)。
295
+ """
296
+ if self._state_bridge is None:
297
+ return
298
+ if isinstance(result, FileUnchangedOutput):
299
+ return
300
+
301
+ inp = ReadToolInput.model_validate(data)
302
+ try:
303
+ mtime_ms = int(os.path.getmtime(inp.file_path) * 1000)
304
+ except OSError:
305
+ mtime_ms = 0
306
+
307
+ self._state_bridge.set_last_read(
308
+ inp.file_path,
309
+ offset=inp.offset,
310
+ limit=inp.limit,
311
+ mtime_ms=mtime_ms,
312
+ )
313
+
314
+ # ------------------------------------------------------------------
315
+ # Hook 9: present
316
+ # ------------------------------------------------------------------
317
+
318
+ def present(
319
+ self, data: dict[str, Any], result: Any, ctx: ToolExecutionContext
320
+ ) -> ToolResultEnvelope:
321
+ """
322
+ 将内部执行结果映射为 ToolResultEnvelope。
323
+ 对应 CC UI.tsx 的 renderToolResultMessage()。
324
+ """
325
+ inp = ReadToolInput.model_validate(data)
326
+ if isinstance(result, TextFileOutput):
327
+ return self._present_text(result)
328
+ if isinstance(result, ImageFileOutput):
329
+ return self._present_image(result)
330
+ if isinstance(result, PDFFileOutput):
331
+ return self._present_pdf(result)
332
+ if isinstance(result, NotebookFileOutput):
333
+ return self._present_notebook(result)
334
+ if isinstance(result, FileUnchangedOutput):
335
+ return self._present_unchanged(result)
336
+
337
+ # fallback(不应发生)
338
+ return ToolResultEnvelope(
339
+ status="ok",
340
+ tool_name=self.name,
341
+ summary=f"Read {inp.file_path}",
342
+ payload=str(result),
343
+ )
344
+
345
+ # ------------------------------------------------------------------
346
+ # 内部辅助
347
+ # ------------------------------------------------------------------
348
+
349
+ def _present_text(self, result: TextFileOutput) -> ToolResultEnvelope:
350
+ if result.total_lines == 0:
351
+ # 空文件
352
+ return ToolResultEnvelope(
353
+ status="ok",
354
+ tool_name=self.name,
355
+ summary=f"Read {result.file_path} (empty file)",
356
+ payload=result.content,
357
+ )
358
+
359
+ lines_read = max(result.line_end - result.line_start + 1, 0)
360
+ remaining = result.total_lines - result.line_end
361
+ summary = (
362
+ f"Read {lines_read} lines from {result.file_path} "
363
+ f"(lines {result.line_start}-{result.line_end} of {result.total_lines})"
364
+ )
365
+
366
+ hints: list[ToolHint] = []
367
+ if remaining > 0:
368
+ next_offset = result.line_end + 1
369
+ hints.append(
370
+ ToolHint(
371
+ message=(
372
+ f"File has {remaining} more lines. "
373
+ f"Call again with offset={next_offset} to continue reading."
374
+ )
375
+ )
376
+ )
377
+
378
+ return ToolResultEnvelope(
379
+ status="ok",
380
+ tool_name=self.name,
381
+ summary=summary,
382
+ payload=result.content,
383
+ hints=hints or None,
384
+ meta={"file_path": result.file_path, "type": "text"},
385
+ )
386
+
387
+ def _present_image(self, result: ImageFileOutput) -> ToolResultEnvelope:
388
+ dims = ""
389
+ if result.width and result.height:
390
+ dims = f" ({result.width}x{result.height})"
391
+ summary = (
392
+ f"Read image from {result.file_path}"
393
+ f"{dims} ({result.original_size:,} bytes, {result.media_type})"
394
+ )
395
+ return ToolResultEnvelope(
396
+ status="ok",
397
+ tool_name=self.name,
398
+ summary=summary,
399
+ payload=result.base64,
400
+ meta={
401
+ "file_path": result.file_path,
402
+ "type": "image",
403
+ "media_type": result.media_type,
404
+ "width": result.width,
405
+ "height": result.height,
406
+ "original_size": result.original_size,
407
+ },
408
+ )
409
+
410
+ def _present_pdf(self, result: PDFFileOutput) -> ToolResultEnvelope:
411
+ pages_info = (
412
+ f"pages {result.page_start}-{result.page_end} of {result.total_pages}"
413
+ )
414
+ summary = f"Read PDF {result.file_path} ({pages_info})"
415
+
416
+ hints: list[ToolHint] = []
417
+ if result.truncated:
418
+ next_start = result.page_end + 1
419
+ hints.append(
420
+ ToolHint(
421
+ message=(
422
+ f"PDF has more pages. "
423
+ f'Call again with pages="{next_start}-..." to continue.'
424
+ )
425
+ )
426
+ )
427
+
428
+ return ToolResultEnvelope(
429
+ status="ok",
430
+ tool_name=self.name,
431
+ summary=summary,
432
+ payload=result.content,
433
+ hints=hints or None,
434
+ meta={
435
+ "file_path": result.file_path,
436
+ "type": "pdf",
437
+ "page_start": result.page_start,
438
+ "page_end": result.page_end,
439
+ "total_pages": result.total_pages,
440
+ },
441
+ )
442
+
443
+ def _present_notebook(self, result: NotebookFileOutput) -> ToolResultEnvelope:
444
+ import json
445
+
446
+ summary = f"Read notebook {result.file_path} ({len(result.cells)} cells)"
447
+ return ToolResultEnvelope(
448
+ status="ok",
449
+ tool_name=self.name,
450
+ summary=summary,
451
+ payload=json.dumps(result.cells, ensure_ascii=False),
452
+ meta={"file_path": result.file_path, "type": "notebook"},
453
+ )
454
+
455
+ def _present_unchanged(self, result: FileUnchangedOutput) -> ToolResultEnvelope:
456
+ return ToolResultEnvelope(
457
+ status="ok",
458
+ tool_name=self.name,
459
+ summary=f"File unchanged: {result.file_path}",
460
+ payload=FILE_UNCHANGED_STUB,
461
+ meta={"file_path": result.file_path, "type": "file_unchanged"},
462
+ )
463
+
464
+ @staticmethod
465
+ def _is_proc_stdio(path: str) -> bool:
466
+ """检查是否为 /proc/self/fd/0-2 或 /proc/<pid>/fd/0-2。"""
467
+ if not path.startswith("/proc/"):
468
+ return False
469
+ return path.endswith(("/fd/0", "/fd/1", "/fd/2"))
470
+
471
+ @staticmethod
472
+ def _find_similar_file(path: str) -> str | None:
473
+ """
474
+ 在同目录下查找名称相似的文件,用于 "Did you mean X?" 提示。
475
+ 对应 CC findSimilarFile(),使用 difflib 序列匹配。
476
+ """
477
+ parent = os.path.dirname(path)
478
+ target = os.path.basename(path)
479
+ if not os.path.isdir(parent):
480
+ return None
481
+ try:
482
+ candidates = os.listdir(parent)
483
+ except OSError:
484
+ return None
485
+ matches = difflib.get_close_matches(target, candidates, n=1, cutoff=0.6)
486
+ if matches:
487
+ return os.path.join(parent, matches[0])
488
+ return None
489
+
490
+ def __repr__(self) -> str:
491
+ return (
492
+ f"<ReadRuntimeTool name={self.name!r} "
493
+ f"[read_only, concurrency_safe, never_truncate]>"
494
+ )