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,621 @@
1
+ """
2
+ runtime/pipeline.py — 工具执行管道与输出大小管控
3
+
4
+ 职责:
5
+ ToolOutputManager:工具输出大小管控,超限时截断并持久化到文件,
6
+ 向模型提供摘要 + overflow_file 路径,防止超大输出膨胀 context window。
7
+
8
+ ToolExecutorPipeline:统一编排工具生命周期的 10 步执行顺序,
9
+ 处理所有短路场景(schema 错误、validate 失败、权限拒绝、执行异常),
10
+ 确保始终返回结构化的 ToolResultEnvelope。
11
+
12
+ PermissionAskInterrupt:check_permissions 返回 ask 且处于交互模式时抛出,
13
+ 由 LangGraph checkpointer + HumanInTheLoopMiddleware 捕获并弹出人机确认。
14
+ headless=True 时 ask 自动降级为 deny,不抛异常。
15
+
16
+ 与 CC 对比:
17
+ CC 的 maxResultSizeChars 方案:当工具输出超过阈值时,将完整内容写入文件,
18
+ Claude 收到 preview + 文件路径。本框架 ToolOutputManager 实现等价逻辑。
19
+ CC 中 Read 工具将 maxResultSizeChars 设为 Infinity(自带分页),
20
+ 对应本框架 RuntimeTool.never_truncate=True。
21
+
22
+ CC 中工具执行由 ToolNode 直接调用工具函数,异常处理分散在各工具内。
23
+ 本框架将完整生命周期收敛到 ToolExecutorPipeline,工具只需实现业务逻辑。
24
+
25
+ CC 中 headless/print 模式通过 shouldAvoidPermissionPrompts=True 使 ask→deny。
26
+ 本框架等价实现:ToolExecutorPipeline(headless=True) 时 ask 结果直接转 blocked。
27
+
28
+ 同参重复调用:
29
+ RuntimeTool.dedupe_identical_calls=True 时,在权限通过后、invoke 前查询
30
+ IdenticalCallMemo;命中则直接返回带 Hint 的 ToolResultEnvelope(不执行 invoke),
31
+ 作为“软提示防重复”策略的执行层硬约束替代。
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import asyncio
37
+ import logging
38
+ import os
39
+ import time
40
+ from typing import Any
41
+
42
+ from pydantic import ValidationError
43
+
44
+ from .base import RuntimeTool
45
+ from .errors import (
46
+ blocked_envelope,
47
+ exception_to_envelope,
48
+ validation_error_envelope,
49
+ )
50
+ from .identical_call_cache import (
51
+ IdenticalCallMemo,
52
+ dedup_cache_key,
53
+ envelope_with_dedup_notice,
54
+ )
55
+ from .models import AuthorizationDecision, ToolExecutionContext, ToolResultEnvelope
56
+ from .resolvers import PermissionResolver
57
+ from langchain_agentx.observability.replay.store import OBS_SCHEMA_VERSION
58
+ from langchain_agentx.observability.logging import (
59
+ ObservabilityLoggerAdapter,
60
+ build_log_context,
61
+ )
62
+
63
+ logger = logging.getLogger(__name__)
64
+
65
+
66
+ # ---------------------------------------------------------------------------
67
+ # PermissionAskInterrupt
68
+ # ---------------------------------------------------------------------------
69
+
70
+ class PermissionAskInterrupt(Exception):
71
+ """
72
+ 已废弃(v2):
73
+ ask 分支迁移为 permission_resolver 回调,不再使用异常承载常态控制流。
74
+
75
+ 保留该类型仅用于兼容历史导入;新代码不应再 raise/catch 此异常。
76
+ """
77
+
78
+ def __init__(
79
+ self,
80
+ tool_name: str,
81
+ ask_prompt: str | None,
82
+ decision: "AuthorizationDecision",
83
+ ) -> None:
84
+ self.tool_name = tool_name
85
+ self.ask_prompt = ask_prompt
86
+ self.decision = decision
87
+ super().__init__(f"[{tool_name}] requires user permission: {ask_prompt or 'confirm to proceed'}")
88
+
89
+
90
+ # ---------------------------------------------------------------------------
91
+ # ToolOutputManager
92
+ # Named Spec: 工具输出大小管控器
93
+ # 对应 CC maxResultSizeChars 方案
94
+ # ---------------------------------------------------------------------------
95
+
96
+ class ToolOutputManager:
97
+ """
98
+ 工具输出大小管控器。
99
+
100
+ 当 envelope 的文本输出超过 max_result_size_chars 时:
101
+ 1. 将完整内容写入 overflow_dir/<tool_call_id>.txt
102
+ 2. 截断 payload 到前 N 字符
103
+ 3. 设置 envelope.truncated = True
104
+ 4. 设置 envelope.overflow_file = 持久化路径
105
+ 5. 在 summary 中追加 "[Full result saved to: <path>]"
106
+
107
+ 对应 CC:
108
+ maxResultSizeChars — 本框架 max_result_size_chars
109
+ Infinity(Read 工具)— 本框架 RuntimeTool.never_truncate=True
110
+ """
111
+
112
+ DEFAULT_MAX_CHARS = 20_000
113
+ OVERFLOW_DIR = ".agent_tool_output"
114
+
115
+ def __init__(
116
+ self,
117
+ max_result_size_chars: int = DEFAULT_MAX_CHARS,
118
+ overflow_dir: str = OVERFLOW_DIR,
119
+ ) -> None:
120
+ self._max_chars = max_result_size_chars
121
+ self._overflow_dir = overflow_dir
122
+
123
+ def truncate_if_needed(
124
+ self,
125
+ envelope: ToolResultEnvelope,
126
+ tool: RuntimeTool,
127
+ tool_call_id: str | None = None,
128
+ ) -> ToolResultEnvelope:
129
+ """
130
+ 检查 envelope 输出大小,超限时截断并持久化。
131
+
132
+ never_truncate=True 的工具(如 ReadRuntimeTool)直接原样返回。
133
+ """
134
+ if tool.never_truncate:
135
+ return envelope
136
+
137
+ max_chars = tool.max_result_size_chars
138
+
139
+ # 将 payload 序列化为文本估算大小
140
+ text = self._envelope_to_text(envelope)
141
+ if len(text) <= max_chars:
142
+ return envelope
143
+
144
+ # 超限:持久化完整内容
145
+ overflow_path = self._persist(text, tool_call_id)
146
+
147
+ # 截断 payload
148
+ truncated_text = text[:max_chars]
149
+ truncated_summary = (
150
+ envelope.summary
151
+ + f"\n[Output truncated at {max_chars} chars."
152
+ + f" Full result saved to: {overflow_path}]"
153
+ )
154
+
155
+ return ToolResultEnvelope(
156
+ status=envelope.status,
157
+ tool_name=envelope.tool_name,
158
+ summary=truncated_summary,
159
+ payload=truncated_text,
160
+ artifacts=envelope.artifacts,
161
+ hints=envelope.hints,
162
+ meta=envelope.meta,
163
+ error=envelope.error,
164
+ truncated=True,
165
+ overflow_file=overflow_path,
166
+ )
167
+
168
+ def _envelope_to_text(self, envelope: ToolResultEnvelope) -> str:
169
+ """将 envelope payload 转为文本用于大小估算。"""
170
+ if envelope.payload is None:
171
+ return envelope.summary
172
+ if isinstance(envelope.payload, str):
173
+ return envelope.payload
174
+ # dict payload:简单序列化
175
+ import json
176
+ try:
177
+ return json.dumps(envelope.payload, ensure_ascii=False)
178
+ except Exception:
179
+ return str(envelope.payload)
180
+
181
+ def _persist(self, text: str, tool_call_id: str | None) -> str:
182
+ """将完整内容写入 overflow_dir,返回文件路径。"""
183
+ os.makedirs(self._overflow_dir, exist_ok=True)
184
+ filename = f"{tool_call_id or int(time.time() * 1000)}.txt"
185
+ path = os.path.join(self._overflow_dir, filename)
186
+ with open(path, "w", encoding="utf-8") as f:
187
+ f.write(text)
188
+ return path
189
+
190
+
191
+ # ---------------------------------------------------------------------------
192
+ # ToolExecutorPipeline
193
+ # Named Spec: 工具执行管道
194
+ # ---------------------------------------------------------------------------
195
+
196
+ class ToolExecutorPipeline:
197
+ """
198
+ 工具执行管道,统一编排工具生命周期的 10 步执行顺序。
199
+
200
+ 执行顺序:
201
+ 1. normalize_input — 输入预处理
202
+ 2. schema parse — Pydantic 校验,失败 → error envelope
203
+ 3. validate_input — 语义校验,失败 → error envelope(模型可重试)
204
+ 4. check_permissions — 权限控制:
205
+ · deny → blocked envelope(不可绕过)
206
+ · ask + headless=True → blocked envelope(ask 降级)
207
+ · ask + headless=False → 抛 PermissionAskInterrupt
208
+ 5. before_invoke — 执行前钩子
209
+ 6. invoke / ainvoke — 核心业务,异常 → error envelope
210
+ 7. after_invoke — 执行后钩子(状态写入、审计)
211
+ 8. state_bridge.update — 状态桥接(如有)
212
+ 9. present — 结果呈现 → ToolResultEnvelope
213
+ 10. output_manager — 输出大小管控
214
+
215
+ headless 模式(API 调用、CI、批处理):
216
+ - check_permissions 返回 ask 时自动降级为 deny(不等待人工确认)
217
+ - 对应 CC 的 shouldAvoidPermissionPrompts=True / isNonInteractiveSession=True
218
+
219
+ 交互模式(headless=False,默认):
220
+ - check_permissions 返回 ask 时抛出 PermissionAskInterrupt
221
+ - 由 LangGraph checkpointer + HumanInTheLoopMiddleware 捕获
222
+
223
+ pipeline 不负责:
224
+ - 将 envelope 映射为 LangChain tool return(由 LangChainAdapter 负责)
225
+ - 决定 ToolNode 路由
226
+ - 管理 agent loop
227
+ """
228
+
229
+ _ASK_HEADLESS_DENY_MSG = (
230
+ "Operation requires interactive user approval, "
231
+ "which is not available in headless/non-interactive mode."
232
+ )
233
+ _ASK_SYNC_DENY_MSG = (
234
+ "Synchronous pipeline does not support interactive permission ask. "
235
+ "Use async pipeline with permission_resolver or run in headless mode."
236
+ )
237
+
238
+ def __init__(
239
+ self,
240
+ *,
241
+ output_manager: ToolOutputManager | None = None,
242
+ headless: bool = False,
243
+ identical_call_memo: IdenticalCallMemo | None = None,
244
+ permission_resolver: PermissionResolver | None = None,
245
+ ) -> None:
246
+ self._output_manager = output_manager or ToolOutputManager()
247
+ self._headless = headless
248
+ self._identical_call_memo = identical_call_memo if identical_call_memo is not None else IdenticalCallMemo()
249
+ self._permission_resolver = permission_resolver
250
+
251
+ def run(
252
+ self,
253
+ *,
254
+ tool: RuntimeTool,
255
+ raw_input: dict[str, Any],
256
+ ctx: ToolExecutionContext,
257
+ ) -> ToolResultEnvelope:
258
+ """同步执行完整 pipeline,始终返回 ToolResultEnvelope。"""
259
+ log = ObservabilityLoggerAdapter(
260
+ logger,
261
+ build_log_context(
262
+ run_id=getattr(ctx, "run_id", None),
263
+ session_id=getattr(ctx, "thread_id", None),
264
+ tool_call_id=getattr(ctx, "tool_call_id", None),
265
+ agent_id=getattr(ctx, "agent_id", None),
266
+ ).as_extra(),
267
+ )
268
+ log.info("tool pipeline run start tool=%s", tool.name)
269
+ try:
270
+ out = self._run(tool=tool, raw_input=raw_input, ctx=ctx)
271
+ log.info("tool pipeline run done tool=%s status=%s", tool.name, out.status)
272
+ return out
273
+ except Exception as exc:
274
+ log.error("tool pipeline run failed tool=%s err=%s", tool.name, exc)
275
+ return exception_to_envelope(exc, tool.name)
276
+
277
+ def set_permission_resolver(self, resolver: PermissionResolver | None) -> None:
278
+ """运行时更新 permission_resolver。"""
279
+ self._permission_resolver = resolver
280
+
281
+ async def arun(
282
+ self,
283
+ *,
284
+ tool: RuntimeTool,
285
+ raw_input: dict[str, Any],
286
+ ctx: ToolExecutionContext,
287
+ ) -> ToolResultEnvelope:
288
+ """异步执行完整 pipeline,始终返回 ToolResultEnvelope。"""
289
+ log = ObservabilityLoggerAdapter(
290
+ logger,
291
+ build_log_context(
292
+ run_id=getattr(ctx, "run_id", None),
293
+ session_id=getattr(ctx, "thread_id", None),
294
+ tool_call_id=getattr(ctx, "tool_call_id", None),
295
+ agent_id=getattr(ctx, "agent_id", None),
296
+ ).as_extra(),
297
+ )
298
+ log.info("tool pipeline arun start tool=%s", tool.name)
299
+ try:
300
+ out = await self._arun(tool=tool, raw_input=raw_input, ctx=ctx)
301
+ log.info("tool pipeline arun done tool=%s status=%s", tool.name, out.status)
302
+ return out
303
+ except Exception as exc:
304
+ log.error("tool pipeline arun failed tool=%s err=%s", tool.name, exc)
305
+ return exception_to_envelope(exc, tool.name)
306
+
307
+ # ---- 内部实现 ----
308
+
309
+ def _run(
310
+ self,
311
+ *,
312
+ tool: RuntimeTool,
313
+ raw_input: dict[str, Any],
314
+ ctx: ToolExecutionContext,
315
+ ) -> ToolResultEnvelope:
316
+ # Step 1: normalize_input
317
+ normalized = tool.normalize_input(raw_input, ctx)
318
+
319
+ # Step 2: schema parse
320
+ parsed_data, schema_error = self._parse_schema(tool, normalized)
321
+ if schema_error is not None:
322
+ return schema_error
323
+
324
+ # Step 3: validate_input
325
+ validation = tool.validate_input(parsed_data, ctx)
326
+ if not validation.ok:
327
+ return validation_error_envelope(
328
+ tool.name,
329
+ validation.message or "Input validation failed.",
330
+ code=validation.code,
331
+ )
332
+
333
+ # Step 4: check_permissions
334
+ auth = tool.check_permissions(parsed_data, ctx)
335
+ if auth.behavior == "deny":
336
+ logger.warning("tool permission denied tool=%s policy_id=%s", tool.name, auth.policy_id)
337
+ env = blocked_envelope(
338
+ tool.name,
339
+ auth.message or "Permission denied.",
340
+ policy_id=auth.policy_id,
341
+ )
342
+ self._attach_runtime_observability(
343
+ env,
344
+ ctx=ctx,
345
+ events=[
346
+ {"event": "permission_checked", "behavior": "deny", "policy_id": auth.policy_id},
347
+ ],
348
+ decision_trace=(auth.metadata or {}).get("decision_trace"),
349
+ )
350
+ return env
351
+ if auth.behavior == "ask":
352
+ logger.warning(
353
+ "tool permission requires ask tool=%s headless=%s policy_id=%s",
354
+ tool.name,
355
+ self._headless,
356
+ auth.policy_id,
357
+ )
358
+ env = blocked_envelope(
359
+ tool.name,
360
+ self._ASK_HEADLESS_DENY_MSG if self._headless else self._ASK_SYNC_DENY_MSG,
361
+ policy_id=auth.policy_id,
362
+ )
363
+ self._attach_runtime_observability(
364
+ env,
365
+ ctx=ctx,
366
+ events=[
367
+ {"event": "permission_checked", "behavior": "ask_sync_denied", "policy_id": auth.policy_id},
368
+ ],
369
+ decision_trace=(auth.metadata or {}).get("decision_trace"),
370
+ )
371
+ return env
372
+ # allow:可能有 updated_input(策略层路径规范化回写)
373
+ if auth.updated_input:
374
+ parsed_data = auth.updated_input
375
+
376
+ dedup_key: str | None = None
377
+ if getattr(tool, "dedupe_identical_calls", False):
378
+ dedup_key = dedup_cache_key(ctx, tool.name, parsed_data)
379
+ cached = self._identical_call_memo.get(dedup_key)
380
+ if cached is not None:
381
+ out = envelope_with_dedup_notice(cached, ctx=ctx)
382
+ self._attach_runtime_observability(
383
+ out,
384
+ ctx=ctx,
385
+ events=[
386
+ {"event": "identical_call_dedup", "behavior": "cache_hit"},
387
+ ],
388
+ decision_trace=(auth.metadata or {}).get("decision_trace"),
389
+ )
390
+ return self._output_manager.truncate_if_needed(
391
+ out, tool, ctx.tool_call_id
392
+ )
393
+
394
+ # Step 5: before_invoke
395
+ tool.before_invoke(parsed_data, ctx)
396
+
397
+ # Step 6: invoke
398
+ result = tool.invoke(parsed_data, ctx)
399
+
400
+ # Step 7: after_invoke
401
+ tool.after_invoke(parsed_data, result, ctx)
402
+
403
+ # Step 9: present
404
+ envelope = tool.present(parsed_data, result, ctx)
405
+ self._attach_runtime_observability(
406
+ envelope,
407
+ ctx=ctx,
408
+ events=[
409
+ {"event": "permission_checked", "behavior": "allow", "policy_id": auth.policy_id},
410
+ {"event": "invoke_completed", "tool_name": tool.name},
411
+ ],
412
+ decision_trace=(auth.metadata or {}).get("decision_trace"),
413
+ )
414
+
415
+ # Step 10: output_manager
416
+ final = self._output_manager.truncate_if_needed(
417
+ envelope, tool, ctx.tool_call_id
418
+ )
419
+ if dedup_key is not None:
420
+ self._identical_call_memo.put(dedup_key, final)
421
+ return final
422
+
423
+ async def _arun(
424
+ self,
425
+ *,
426
+ tool: RuntimeTool,
427
+ raw_input: dict[str, Any],
428
+ ctx: ToolExecutionContext,
429
+ ) -> ToolResultEnvelope:
430
+ # Step 1: normalize_input
431
+ normalized = tool.normalize_input(raw_input, ctx)
432
+
433
+ # Step 2: schema parse
434
+ parsed_data, schema_error = self._parse_schema(tool, normalized)
435
+ if schema_error is not None:
436
+ return schema_error
437
+
438
+ # Step 3: validate_input
439
+ validation = tool.validate_input(parsed_data, ctx)
440
+ if not validation.ok:
441
+ return validation_error_envelope(
442
+ tool.name,
443
+ validation.message or "Input validation failed.",
444
+ code=validation.code,
445
+ )
446
+
447
+ # Step 4: check_permissions(支持异步 policy)
448
+ if tool._policy is not None and hasattr(tool._policy, "aauthorize"):
449
+ auth = await tool._policy.aauthorize(
450
+ tool_name=tool.name, input_data=parsed_data, ctx=ctx
451
+ )
452
+ else:
453
+ auth = tool.check_permissions(parsed_data, ctx)
454
+
455
+ if auth.behavior == "deny":
456
+ logger.warning("tool permission denied tool=%s policy_id=%s", tool.name, auth.policy_id)
457
+ env = blocked_envelope(
458
+ tool.name,
459
+ auth.message or "Permission denied.",
460
+ policy_id=auth.policy_id,
461
+ )
462
+ self._attach_runtime_observability(
463
+ env,
464
+ ctx=ctx,
465
+ events=[
466
+ {"event": "permission_checked", "behavior": "deny", "policy_id": auth.policy_id},
467
+ ],
468
+ decision_trace=(auth.metadata or {}).get("decision_trace"),
469
+ )
470
+ return env
471
+ if auth.behavior == "ask":
472
+ logger.warning(
473
+ "tool permission requires ask tool=%s headless=%s policy_id=%s",
474
+ tool.name,
475
+ self._headless,
476
+ auth.policy_id,
477
+ )
478
+ if self._headless or self._permission_resolver is None:
479
+ env = blocked_envelope(
480
+ tool.name,
481
+ self._ASK_HEADLESS_DENY_MSG,
482
+ policy_id=auth.policy_id,
483
+ )
484
+ self._attach_runtime_observability(
485
+ env,
486
+ ctx=ctx,
487
+ events=[
488
+ {"event": "permission_checked", "behavior": "ask_auto_denied", "policy_id": auth.policy_id},
489
+ ],
490
+ decision_trace=(auth.metadata or {}).get("decision_trace"),
491
+ )
492
+ return env
493
+ allowed = await self._permission_resolver(tool.name, auth.ask_prompt)
494
+ if not allowed:
495
+ env = blocked_envelope(
496
+ tool.name,
497
+ auth.ask_prompt or "User rejected the operation.",
498
+ policy_id=auth.policy_id,
499
+ )
500
+ self._attach_runtime_observability(
501
+ env,
502
+ ctx=ctx,
503
+ events=[
504
+ {
505
+ "event": "permission_checked",
506
+ "behavior": "ask_rejected_by_resolver",
507
+ "policy_id": auth.policy_id,
508
+ },
509
+ ],
510
+ decision_trace=(auth.metadata or {}).get("decision_trace"),
511
+ )
512
+ return env
513
+ if auth.updated_input:
514
+ parsed_data = auth.updated_input
515
+
516
+ dedup_key: str | None = None
517
+ if getattr(tool, "dedupe_identical_calls", False):
518
+ dedup_key = dedup_cache_key(ctx, tool.name, parsed_data)
519
+ cached = self._identical_call_memo.get(dedup_key)
520
+ if cached is not None:
521
+ out = envelope_with_dedup_notice(cached, ctx=ctx)
522
+ self._attach_runtime_observability(
523
+ out,
524
+ ctx=ctx,
525
+ events=[
526
+ {"event": "identical_call_dedup", "behavior": "cache_hit"},
527
+ ],
528
+ decision_trace=(auth.metadata or {}).get("decision_trace"),
529
+ )
530
+ return self._output_manager.truncate_if_needed(
531
+ out, tool, ctx.tool_call_id
532
+ )
533
+
534
+ # Step 5: before_invoke
535
+ tool.before_invoke(parsed_data, ctx)
536
+
537
+ # Step 6: ainvoke
538
+ result = await tool.ainvoke(parsed_data, ctx)
539
+
540
+ # Step 7: after_invoke
541
+ tool.after_invoke(parsed_data, result, ctx)
542
+
543
+ # Step 9: present
544
+ envelope = tool.present(parsed_data, result, ctx)
545
+ self._attach_runtime_observability(
546
+ envelope,
547
+ ctx=ctx,
548
+ events=[
549
+ {"event": "permission_checked", "behavior": "allow", "policy_id": auth.policy_id},
550
+ {"event": "invoke_completed", "tool_name": tool.name},
551
+ ],
552
+ decision_trace=(auth.metadata or {}).get("decision_trace"),
553
+ )
554
+
555
+ # Step 10: output_manager
556
+ final = self._output_manager.truncate_if_needed(
557
+ envelope, tool, ctx.tool_call_id
558
+ )
559
+ if dedup_key is not None:
560
+ self._identical_call_memo.put(dedup_key, final)
561
+ return final
562
+
563
+ @staticmethod
564
+ def _parse_schema(
565
+ tool: RuntimeTool,
566
+ data: dict[str, Any],
567
+ ) -> tuple[dict[str, Any], ToolResultEnvelope | None]:
568
+ """
569
+ 用 tool.input_model 做 Pydantic schema 解析。
570
+ 成功返回 (parsed_dict, None),失败返回 ({}, error_envelope)。
571
+ """
572
+ try:
573
+ parsed = tool.input_model.model_validate(data)
574
+ return parsed.model_dump(), None
575
+ except ValidationError as exc:
576
+ detail = "; ".join(
577
+ f"{'.'.join(str(l) for l in e['loc'])}: {e['msg']}"
578
+ for e in exc.errors()
579
+ )
580
+ envelope = validation_error_envelope(
581
+ tool.name,
582
+ f"Input schema error: {detail}",
583
+ code="INPUT_SCHEMA_ERROR",
584
+ )
585
+ return {}, envelope
586
+ except Exception as exc:
587
+ return {}, exception_to_envelope(exc, tool.name)
588
+
589
+ @staticmethod
590
+ def _attach_runtime_observability(
591
+ envelope: ToolResultEnvelope,
592
+ *,
593
+ ctx: ToolExecutionContext,
594
+ events: list[dict[str, Any]],
595
+ decision_trace: list[dict[str, Any]] | None = None,
596
+ ) -> None:
597
+ meta = dict(envelope.meta or {})
598
+ obs = meta.get("observability")
599
+ if not isinstance(obs, dict):
600
+ obs = {
601
+ "schema_version": OBS_SCHEMA_VERSION,
602
+ "trace_context": {
603
+ "thread_id": ctx.thread_id,
604
+ "run_id": ctx.run_id,
605
+ "tool_call_id": ctx.tool_call_id,
606
+ "tool_name": ctx.tool_name,
607
+ },
608
+ "scenario": "runtime_pipeline",
609
+ "events": [],
610
+ }
611
+ merged_events = list(obs.get("events", []))
612
+ start_seq = len(merged_events)
613
+ for i, ev in enumerate(events, start=1):
614
+ merged_events.append({"seq": start_seq + i, **ev})
615
+ obs["events"] = merged_events
616
+ meta["observability"] = obs
617
+ if decision_trace:
618
+ meta.setdefault("decision_trace", decision_trace)
619
+ if ctx.tool_call_id:
620
+ meta.setdefault("tool_call_id", ctx.tool_call_id)
621
+ envelope.meta = meta