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,319 @@
1
+ """
2
+ runtime/base.py — RuntimeTool 抽象基类
3
+
4
+ 职责:
5
+ 定义框架内部工具的核心 OOP 契约。每个工具继承 RuntimeTool,
6
+ 只需实现 invoke(),其余生命周期 hook 均有 fail-open 默认实现。
7
+ 框架通过 ToolExecutorPipeline 统一编排这些 hook,工具无需自行
8
+ 实现完整控制流。
9
+
10
+ 与 CC 对比:
11
+ CC 中 Tool<Input, Output> 是 TypeScript interface,通过 buildTool(def)
12
+ 工厂函数注入默认实现(TOOL_DEFAULTS),checkPermissions 默认 allow,
13
+ validateInput 默认 success。
14
+ 本框架用 Python ABC 实现等价模式:hook 方法有 fail-open 默认实现,
15
+ 子类只覆盖关心的部分。is_read_only / is_destructive / is_concurrency_safe
16
+ 对应 CC Tool 的同名 flag。never_truncate 对应 CC maxResultSizeChars=Infinity。
17
+
18
+ 一个工具的完整流转(OOP 视角):
19
+
20
+ ┌─────────────────────────────────────────────────────────────────┐
21
+ │ RuntimeToolRegistry │
22
+ │ register(tool) ──► LangChainAdapter.build_structured_tool() │
23
+ │ │ │
24
+ │ ▼ │
25
+ │ StructuredTool ◄── model.bind_tools() │
26
+ └─────────────────────────────────────────────────────────────────┘
27
+
28
+ AIMessage.tool_calls[i]
29
+
30
+
31
+ ┌─────────────────────────────────────────────────────────────────┐
32
+ │ ToolNode │
33
+ │ StructuredTool.func(state, config, **kwargs) │
34
+ └─────────────────────────────────────────────────────────────────┘
35
+
36
+
37
+ ┌─────────────────────────────────────────────────────────────────┐
38
+ │ LangChainAdapter │
39
+ │ build_tool_execution_context(tool_name, raw_input, │
40
+ │ state, config) │
41
+ │ → ToolExecutionContext │
42
+ └─────────────────────────────────────────────────────────────────┘
43
+
44
+
45
+ ┌─────────────────────────────────────────────────────────────────┐
46
+ │ ToolExecutorPipeline.run() │
47
+ │ │
48
+ │ [Hook 1] tool.normalize_input(raw, ctx) │
49
+ │ │ 路径规范化、默认值填充 │
50
+ │ ▼ │
51
+ │ [Step 2] input_model.model_validate(normalized) │
52
+ │ │ Pydantic schema 解析,失败 → error envelope │
53
+ │ ▼ │
54
+ │ [Hook 3] tool.validate_input(data, ctx) │
55
+ │ │ 语义校验(文件存在?是否目录?) │
56
+ │ │ 失败 → error envelope(模型可修正重试) │
57
+ │ ▼ │
58
+ │ [Hook 4] tool.check_permissions(data, ctx) │
59
+ │ │ 权限控制(路径越界?策略拒绝?) │
60
+ │ │ 失败 → blocked envelope(不可绕过) │
61
+ │ ▼ │
62
+ │ [Hook 5] tool.before_invoke(data, ctx) │
63
+ │ │ 执行前钩子(日志、预热等) │
64
+ │ ▼ │
65
+ │ [Core ] tool.invoke(data, ctx) / tool.ainvoke(data, ctx) │
66
+ │ │ 核心业务执行,异常 → exception_to_envelope() │
67
+ │ ▼ │
68
+ │ [Hook 7] tool.after_invoke(data, result, ctx) │
69
+ │ │ 状态写入(last_read_map)、审计记录 │
70
+ │ ▼ │
71
+ │ [Step 8] state_bridge.update(ctx, result) │
72
+ │ ▼ │
73
+ │ [Hook 9] tool.present(data, result, ctx) │
74
+ │ │ → ToolResultEnvelope │
75
+ │ ▼ │
76
+ │ [Step10] output_manager.truncate_if_needed(envelope) │
77
+ │ │ 超大输出 → 写文件 + 更新 summary + overflow_file │
78
+ │ ▼ │
79
+ │ ToolResultEnvelope │
80
+ └─────────────────────────────────────────────────────────────────┘
81
+
82
+
83
+ ┌─────────────────────────────────────────────────────────────────┐
84
+ │ LangChainAdapter │
85
+ │ envelope_to_tool_output(envelope) → str │
86
+ │ ok: summary + payload + hints │
87
+ │ blocked: "Permission denied: ..." │
88
+ │ error: "Error: ..." │
89
+ └─────────────────────────────────────────────────────────────────┘
90
+
91
+
92
+ ToolMessage(content=str, tool_call_id=...)
93
+
94
+
95
+ agent loop / loop_controller
96
+ """
97
+
98
+ from __future__ import annotations
99
+
100
+ import asyncio
101
+ from abc import ABC, abstractmethod
102
+ from typing import Any
103
+
104
+ from .models import (
105
+ AuthorizationDecision,
106
+ ToolExecutionContext,
107
+ ToolHint,
108
+ ToolResultEnvelope,
109
+ ValidationResult,
110
+ )
111
+
112
+
113
+ class RuntimeTool(ABC):
114
+ """
115
+ 工具运行时抽象基类。
116
+
117
+ 硬约定(Hook 输入契约):
118
+ RuntimeTool 的所有生命周期 hook 都必须使用 ``dict[str, Any]`` 作为
119
+ ``data`` 形态,禁止依赖属性访问(如 ``data.command``)。
120
+ 这是与 ToolExecutorPipeline 的固定契约:schema 解析后统一下发 dict。
121
+
122
+ 子类必须定义:
123
+ name — 工具名(唯一,面向模型暴露)
124
+ description — 工具描述(面向模型暴露)
125
+ input_model — Pydantic BaseModel,直接作为 StructuredTool.args_schema
126
+
127
+ 子类可覆盖的生命周期 hook(默认均为 fail-open / no-op):
128
+ normalize_input — 输入预处理(路径规范化、默认值填充)
129
+ validate_input — 语义校验(失败告知模型,可重试)
130
+ check_permissions — 权限控制(失败策略拒绝,不可绕过)
131
+ before_invoke — 执行前钩子
132
+ after_invoke — 执行后钩子(状态写入、审计)
133
+ present — 结果呈现(构造 ToolResultEnvelope)
134
+
135
+ 子类必须实现:
136
+ invoke — 核心业务执行(同步)
137
+
138
+ 子类可选覆盖:
139
+ ainvoke — 核心业务执行(异步),默认委托 executor 运行 invoke
140
+ """
141
+
142
+ # ---- 子类必须定义的类属性 ----
143
+ name: str
144
+ description: str
145
+
146
+ # input_model 必须是 pydantic BaseModel 子类
147
+ # 直接作为 StructuredTool.args_schema 暴露给模型
148
+ input_model: type
149
+
150
+ output_model: type | None = None
151
+
152
+ # ---- 行为标志(对应 CC Tool 的同名 flag)----
153
+ is_read_only: bool = False
154
+ """只读工具,不修改文件系统。"""
155
+
156
+ is_destructive: bool = False
157
+ """高风险写操作(write / edit / bash 等)。"""
158
+
159
+ is_concurrency_safe: bool = False
160
+ """是否支持并发执行。"""
161
+
162
+ never_truncate: bool = False
163
+ """
164
+ 禁止 ToolOutputManager 截断输出。
165
+ 对应 CC maxResultSizeChars=Infinity。
166
+ 适用于自带分页机制的工具(如 ReadRuntimeTool 用 limit 参数控制)。
167
+ """
168
+
169
+ dedupe_identical_calls: bool = False
170
+ """
171
+ 为 True 时,``ToolExecutorPipeline`` 在同 ``thread_id`` 下对「工具名 + 解析后参数字典」
172
+ 命中缓存则直接返回上一次成功的 ``ToolResultEnvelope``(带 Hint),不再次 ``invoke``。
173
+ 仅适用于只读、无副作用重复的安全工具;写/破坏性工具须保持 False。
174
+ """
175
+
176
+ max_result_size_chars: int = 20_000
177
+ """
178
+ 单次输出字符上限,超出时由 ToolOutputManager 截断并持久化。
179
+ 对应 CC Tool.maxResultSizeChars。
180
+ """
181
+
182
+ def __init__(
183
+ self,
184
+ *,
185
+ policy: Any | None = None,
186
+ state_bridge: Any | None = None,
187
+ ) -> None:
188
+ self._policy = policy
189
+ self._state_bridge = state_bridge
190
+
191
+ # ---- 生命周期 Hook(默认 fail-open / no-op,子类按需覆盖)----
192
+
193
+ def normalize_input(
194
+ self,
195
+ raw: dict[str, Any],
196
+ ctx: ToolExecutionContext,
197
+ ) -> dict[str, Any]:
198
+ """
199
+ 输入预处理:路径规范化、默认值填充、参数 clamp 等。
200
+ 默认原样返回,子类覆盖时应返回修改后的新 dict。
201
+ """
202
+ return raw
203
+
204
+ def validate_input(
205
+ self,
206
+ data: dict[str, Any],
207
+ ctx: ToolExecutionContext,
208
+ ) -> ValidationResult:
209
+ """
210
+ 语义校验:参数格式、文件是否存在、是否为目录等。
211
+
212
+ 失败时 pipeline 生成 status="error" 的 envelope,
213
+ 模型收到 message 后可修正输入重试。
214
+
215
+ 默认 ok=True(fail-open,对应 CC buildTool TOOL_DEFAULTS)。
216
+ 注意:这里的 data 恒为 dict,工具实现不得使用 data.xxx 属性访问。
217
+ """
218
+ return ValidationResult(ok=True)
219
+
220
+ def check_permissions(
221
+ self,
222
+ data: dict[str, Any],
223
+ ctx: ToolExecutionContext,
224
+ ) -> AuthorizationDecision:
225
+ """
226
+ 权限控制:路径越界、策略拒绝等授权检查。
227
+
228
+ 失败时 pipeline 生成 status="blocked" 的 envelope,
229
+ 模型知晓被拒但不获得路径细节(策略不透明原则)。
230
+
231
+ 默认实现:委托注入的 PolicyEngine。
232
+ - 注入了 policy → 自动受 policy 保护(所有工具无需覆盖此方法即可生效)
233
+ - 未注入 policy → 默认 allow(开发模式,fail-open)
234
+ 工具有特殊需求(如 BashRuntimeTool 的命令级检查)可覆盖此方法,
235
+ 在工具级检查后调用 super().check_permissions(data, ctx) 走通用 policy。
236
+ """
237
+ if self._policy is not None:
238
+ return self._policy.authorize(
239
+ tool_name=self.name,
240
+ input_data=data,
241
+ ctx=ctx,
242
+ )
243
+ return AuthorizationDecision(behavior="allow")
244
+
245
+ def before_invoke(
246
+ self,
247
+ data: dict[str, Any],
248
+ ctx: ToolExecutionContext,
249
+ ) -> None:
250
+ """执行前钩子(日志、预热等),默认 no-op。"""
251
+
252
+ @abstractmethod
253
+ def invoke(
254
+ self,
255
+ data: dict[str, Any],
256
+ ctx: ToolExecutionContext,
257
+ ) -> Any:
258
+ """
259
+ 核心业务执行(同步)。子类必须实现。
260
+
261
+ 返回值将传入 after_invoke 和 present,类型由子类定义。
262
+ 抛出的异常由 pipeline 统一捕获并映射为 error envelope。
263
+ """
264
+ ...
265
+
266
+ async def ainvoke(
267
+ self,
268
+ data: dict[str, Any],
269
+ ctx: ToolExecutionContext,
270
+ ) -> Any:
271
+ """
272
+ 核心业务执行(异步)。
273
+
274
+ 默认在 executor 中运行同步 invoke,保持 sync/async 对称。
275
+ 有真正异步 I/O 需求的工具(如 HTTP 请求)可覆盖此方法。
276
+ """
277
+ loop = asyncio.get_event_loop()
278
+ return await loop.run_in_executor(None, lambda: self.invoke(data, ctx))
279
+
280
+ def after_invoke(
281
+ self,
282
+ data: dict[str, Any],
283
+ result: Any,
284
+ ctx: ToolExecutionContext,
285
+ ) -> None:
286
+ """
287
+ 执行后钩子:状态写入(last_read_map)、审计记录等。
288
+ 默认 no-op,ReadRuntimeTool 等工具在此更新 StateBridge。
289
+ """
290
+
291
+ def present(
292
+ self,
293
+ data: dict[str, Any],
294
+ result: Any,
295
+ ctx: ToolExecutionContext,
296
+ ) -> ToolResultEnvelope:
297
+ """
298
+ 将内部执行结果映射为 ToolResultEnvelope。
299
+
300
+ 子类应覆盖此方法,提供语义化的 summary / payload / hints。
301
+ 默认实现将 result 转为 str 作为 summary,适合快速原型。
302
+ """
303
+ return ToolResultEnvelope(
304
+ status="ok",
305
+ tool_name=self.name,
306
+ summary=str(result),
307
+ payload={"raw": str(result)},
308
+ )
309
+
310
+ def __repr__(self) -> str:
311
+ flags = []
312
+ if self.is_read_only:
313
+ flags.append("read_only")
314
+ if self.is_destructive:
315
+ flags.append("destructive")
316
+ if self.never_truncate:
317
+ flags.append("never_truncate")
318
+ flag_str = f"[{', '.join(flags)}]" if flags else ""
319
+ return f"<{type(self).__name__} name={self.name!r} {flag_str}>".strip()
@@ -0,0 +1,190 @@
1
+ """
2
+ runtime/errors.py — 工具运行时错误类型与 envelope 映射
3
+
4
+ 职责:
5
+ 定义框架级运行时异常类型,并提供统一的异常 → ToolResultEnvelope 映射函数。
6
+ 所有工具执行过程中未被捕获的异常,均通过 exception_to_envelope() 转换为
7
+ status="error" 的 envelope,确保 pipeline 始终返回结构化结果。
8
+
9
+ 与 CC 对比:
10
+ CC 中工具执行异常直接在 ToolNode 层被捕获并转为 ToolMessage(content=error_str)。
11
+ 本框架将异常映射提前到 pipeline 内部,保持 envelope 协议的完整性,
12
+ 同时将 traceback 隔离在 meta 中,不暴露给模型。
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import traceback as tb
18
+
19
+ from .models import ToolErrorInfo, ToolResultEnvelope
20
+
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # 框架级异常类型
24
+ # ---------------------------------------------------------------------------
25
+
26
+ class ToolRuntimeError(Exception):
27
+ """工具运行时框架基础异常。"""
28
+
29
+ def __init__(self, message: str, code: str | None = None) -> None:
30
+ super().__init__(message)
31
+ self.message = message
32
+ self.code = code
33
+
34
+
35
+ class ToolRegistrationError(ToolRuntimeError):
36
+ """工具注册失败,通常因为工具名重复。"""
37
+
38
+ def __init__(self, tool_name: str) -> None:
39
+ super().__init__(
40
+ f"Tool '{tool_name}' is already registered. Tool names must be unique.",
41
+ code="TOOL_ALREADY_REGISTERED",
42
+ )
43
+ self.tool_name = tool_name
44
+
45
+
46
+ class ToolNotFoundError(ToolRuntimeError):
47
+ """工具查询失败,工具名不存在于 registry。"""
48
+
49
+ def __init__(self, tool_name: str) -> None:
50
+ super().__init__(
51
+ f"Tool '{tool_name}' not found in registry.",
52
+ code="TOOL_NOT_FOUND",
53
+ )
54
+ self.tool_name = tool_name
55
+
56
+
57
+ class ToolInputSchemaError(ToolRuntimeError):
58
+ """输入参数不符合 input_model schema,由 pipeline schema parse 阶段抛出。"""
59
+
60
+ def __init__(self, tool_name: str, detail: str) -> None:
61
+ super().__init__(
62
+ f"Input schema validation failed for tool '{tool_name}': {detail}",
63
+ code="INPUT_SCHEMA_ERROR",
64
+ )
65
+ self.tool_name = tool_name
66
+ self.detail = detail
67
+
68
+
69
+ # ---------------------------------------------------------------------------
70
+ # 异常 → ToolResultEnvelope 映射
71
+ # ---------------------------------------------------------------------------
72
+
73
+ def exception_to_envelope(
74
+ exc: Exception,
75
+ tool_name: str,
76
+ *,
77
+ include_traceback: bool = True,
78
+ ) -> ToolResultEnvelope:
79
+ """
80
+ 将任意异常映射为 status="error" 的 ToolResultEnvelope。
81
+
82
+ traceback 写入 meta["traceback"],不进入模型上下文。
83
+ 面向模型的 summary 只包含简短错误说明,不暴露内部细节。
84
+
85
+ Args:
86
+ exc: 捕获到的异常对象。
87
+ tool_name: 当前执行的工具名。
88
+ include_traceback: 是否将 traceback 写入 meta(默认 True,用于调试)。
89
+
90
+ Returns:
91
+ status="error" 的 ToolResultEnvelope。
92
+ """
93
+ exc_type = type(exc).__name__
94
+ message = str(exc) if str(exc) else exc_type
95
+
96
+ # ToolRuntimeError 子类携带稳定 code
97
+ code = exc.code if isinstance(exc, ToolRuntimeError) else None
98
+
99
+ error_info = ToolErrorInfo(
100
+ message=message,
101
+ code=code,
102
+ exception_type=exc_type,
103
+ )
104
+
105
+ meta: dict = {"exception_type": exc_type}
106
+ if include_traceback:
107
+ meta["traceback"] = tb.format_exc()
108
+
109
+ return ToolResultEnvelope(
110
+ status="error",
111
+ tool_name=tool_name,
112
+ summary=f"Error: {message}",
113
+ error=error_info,
114
+ meta=meta,
115
+ )
116
+
117
+
118
+ def blocked_envelope(
119
+ tool_name: str,
120
+ message: str,
121
+ policy_id: str | None = None,
122
+ ) -> ToolResultEnvelope:
123
+ """
124
+ 构造 status="blocked" 的 ToolResultEnvelope。
125
+
126
+ check_permissions() 返回 deny 时由 pipeline 调用。
127
+ 向模型返回通用拒绝信息,不暴露路径细节(策略不透明原则)。
128
+ """
129
+ return ToolResultEnvelope(
130
+ status="blocked",
131
+ tool_name=tool_name,
132
+ summary=f"Permission denied: {message}",
133
+ meta={"policy_id": policy_id} if policy_id else None,
134
+ )
135
+
136
+
137
+ def validation_error_envelope(
138
+ tool_name: str,
139
+ message: str,
140
+ code: str | None = None,
141
+ ) -> ToolResultEnvelope:
142
+ """
143
+ 构造 validate_input 失败的 status="error" ToolResultEnvelope。
144
+
145
+ 模型会收到 message,可据此修正输入后重试。
146
+ """
147
+ return ToolResultEnvelope(
148
+ status="error",
149
+ tool_name=tool_name,
150
+ summary=f"Error: {message}",
151
+ error=ToolErrorInfo(message=message, code=code),
152
+ )
153
+
154
+
155
+ def blocked_envelope(
156
+ tool_name: str,
157
+ message: str,
158
+ policy_id: str | None = None,
159
+ ) -> ToolResultEnvelope:
160
+ """
161
+ 构造 status="blocked" 的 ToolResultEnvelope。
162
+
163
+ check_permissions() 返回 deny 时由 pipeline 调用。
164
+ 向模型返回通用拒绝信息,不暴露路径细节(策略不透明原则)。
165
+ """
166
+ return ToolResultEnvelope(
167
+ status="blocked",
168
+ tool_name=tool_name,
169
+ summary=f"Permission denied: {message}",
170
+ meta={"policy_id": policy_id} if policy_id else None,
171
+ )
172
+
173
+
174
+ def validation_error_envelope(
175
+ tool_name: str,
176
+ message: str,
177
+ code: str | None = None,
178
+ ) -> ToolResultEnvelope:
179
+ """
180
+ 构造 validate_input 失败的 status="error" ToolResultEnvelope。
181
+
182
+ 模型会收到 message,可据此修正输入后重试。
183
+ 与 exception_to_envelope 的区别:无 traceback,面向模型可读。
184
+ """
185
+ return ToolResultEnvelope(
186
+ status="error",
187
+ tool_name=tool_name,
188
+ summary=f"Error: {message}",
189
+ error=ToolErrorInfo(message=message, code=code),
190
+ )
@@ -0,0 +1,110 @@
1
+ """
2
+ 同工具 + 同解析后参数 的会话内结果复用(替代仅靠 middleware 注入「别重复调」的软提示)。
3
+
4
+ - 按 ``(thread_id, tool_name, sha256(sorted_json(args)))`` 做键;命中则 **不再次 invoke**,
5
+ 直接返回上一次的 ``ToolResultEnvelope`` 副本,并打上 ``meta.identical_call_dedup`` 与 Hint。
6
+ - 仅 ``RuntimeTool.dedupe_identical_calls=True`` 时启用;写类/破坏性工具应保持 False。
7
+
8
+ 由 ``ToolExecutorPipeline`` 在权限通过之后、``before_invoke`` 之前查询;成功 ``ok`` 后写入缓存。
9
+
10
+ 示例(直观看这个模块在做什么):
11
+
12
+ 1) 同一线程、同工具、同参数:第二次直接复用,不再执行工具
13
+
14
+ - 第一次调用:``thread_id=t1, tool=read, args={\"file_path\":\"/a.txt\"}``
15
+ -> 真正执行 read 工具,返回 envelope,写入 memo
16
+ - 第二次调用:``thread_id=t1, tool=read, args={\"file_path\":\"/a.txt\"}``
17
+ -> 命中 memo,直接返回上次 envelope(并标记 ``identical_call_dedup=True``)
18
+
19
+ 2) 不同线程隔离:不会串缓存
20
+
21
+ - 调用 A:``thread_id=t1, tool=read, args={\"file_path\":\"/a.txt\"}``
22
+ - 调用 B:``thread_id=t2, tool=read, args={\"file_path\":\"/a.txt\"}``
23
+ - 因 thread_id 不同,键不同,B 不会命中 A 的缓存,仍会执行工具
24
+
25
+ 3) 参数不同:不会误命中
26
+
27
+ - ``args={\"file_path\":\"/a.txt\"}`` 与 ``args={\"file_path\":\"/b.txt\"}``
28
+ - 参数摘要不同,键不同,仍会执行工具
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import copy
34
+ import hashlib
35
+ import json
36
+ from collections import OrderedDict
37
+ from typing import Any
38
+
39
+ from .models import ToolExecutionContext, ToolHint, ToolResultEnvelope
40
+
41
+
42
+ def dedup_cache_key(
43
+ ctx: ToolExecutionContext,
44
+ tool_name: str,
45
+ parsed_data: dict[str, Any],
46
+ ) -> str:
47
+ """稳定键:线程隔离 + 工具名 + 参数字典规范化后的摘要。"""
48
+ tid = ctx.thread_id or "__no_thread__"
49
+ payload = json.dumps(parsed_data, sort_keys=True, ensure_ascii=False)
50
+ digest = hashlib.sha256(payload.encode("utf-8")).hexdigest()
51
+ return f"{tid}::{tool_name}::{digest}"
52
+
53
+
54
+ def envelope_with_dedup_notice(
55
+ env: ToolResultEnvelope,
56
+ *,
57
+ ctx: ToolExecutionContext,
58
+ ) -> ToolResultEnvelope:
59
+ """返回深拷贝,并附加 dedup 元数据与面向模型的 Hint(会进入 ``envelope_to_tool_output``)。"""
60
+ out = copy.deepcopy(env)
61
+ meta = dict(out.meta or {})
62
+ meta["identical_call_dedup"] = True
63
+ meta["dedup_source_tool_call_id"] = ctx.tool_call_id
64
+ out.meta = meta
65
+ hints = list(out.hints or [])
66
+ hints.insert(
67
+ 0,
68
+ ToolHint(
69
+ message=(
70
+ "Same tool and arguments as an earlier call in this thread; "
71
+ "reusing the previous successful result without executing again."
72
+ ),
73
+ ),
74
+ )
75
+ out.hints = hints
76
+ return out
77
+
78
+
79
+ class IdenticalCallMemo:
80
+ """
81
+ 进程内 LRU(按插入顺序淘汰):仅缓存 ``status == \"ok\"`` 的 envelope。
82
+ """
83
+
84
+ def __init__(self, max_entries: int = 512) -> None:
85
+ self._max_entries = max(1, int(max_entries))
86
+ self._data: OrderedDict[str, ToolResultEnvelope] = OrderedDict()
87
+
88
+ def get(self, key: str) -> ToolResultEnvelope | None:
89
+ if key not in self._data:
90
+ return None
91
+ self._data.move_to_end(key)
92
+ return copy.deepcopy(self._data[key])
93
+
94
+ def put(self, key: str, envelope: ToolResultEnvelope) -> None:
95
+ if envelope.status != "ok":
96
+ return
97
+ self._data[key] = copy.deepcopy(envelope)
98
+ self._data.move_to_end(key)
99
+ while len(self._data) > self._max_entries:
100
+ self._data.popitem(last=False)
101
+
102
+ def clear(self) -> None:
103
+ self._data.clear()
104
+
105
+
106
+ __all__ = [
107
+ "IdenticalCallMemo",
108
+ "dedup_cache_key",
109
+ "envelope_with_dedup_notice",
110
+ ]