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,47 @@
1
+ """
2
+ LangChain AgentX Agent - 基于 LangGraph 实现的 LangChain AgentX 风格 Agent
3
+
4
+ 核心特性:
5
+ 1. 使用原生 finish_reason 判断退出(与 OpenCode 一致)
6
+ 2. 与 LangGraph 生态完全兼容
7
+ 3. 解决 create_agent 的"工具调用噩梦"
8
+
9
+ 使用方式:
10
+ ```python
11
+ from langchain_agentx.loop import create_loop_agent
12
+ from langchain_agentx.tool_runtime import ToolRuntimeLoader
13
+ # from langchain_agentx.tools.read import ReadRuntimeTool
14
+
15
+ loader = ToolRuntimeLoader()
16
+ # loader.register(ReadRuntimeTool())
17
+
18
+ agent = create_loop_agent(
19
+ model="anthropic:claude-sonnet-4-5-20250929",
20
+ loader=loader,
21
+ system_prompt="You are a helpful assistant",
22
+ max_steps=50,
23
+ )
24
+
25
+ for chunk in agent.stream({"messages": [{"role": "user", "content": "Hello"}]}):
26
+ print(chunk)
27
+ ```
28
+
29
+ 参考设计文档:
30
+ - .claude/design/lowest-layer-opencode-style-agent-design.md(原理和问题)
31
+ - .claude/design/create-opencode-style-agent-design.md(实现方案)
32
+ """
33
+
34
+ # 主要导出
35
+ from .graph.factory import (
36
+ create_loop_agent,
37
+ )
38
+ from .loop_abort import LoopAbortSignal
39
+ from .config import build_agent_config
40
+
41
+ __all__ = [
42
+ # 主要接口
43
+ "create_loop_agent",
44
+ "LoopAbortSignal",
45
+ # 配置助手
46
+ "build_agent_config",
47
+ ]
@@ -0,0 +1,20 @@
1
+ from .agent_config import build_agent_config
2
+ from .agent_loop_config import AgentLoopConfig
3
+ from .model_context_resolver import ModelContextResolver
4
+ from .token_estimator import TokenEstimator
5
+ from .runtime_settings import (
6
+ compute_recursion_limit,
7
+ materialize_max_steps,
8
+ resolve_max_steps,
9
+ )
10
+
11
+ __all__ = [
12
+ "AgentLoopConfig",
13
+ "ModelContextResolver",
14
+ "TokenEstimator",
15
+ "build_agent_config",
16
+ "compute_recursion_limit",
17
+ "materialize_max_steps",
18
+ "resolve_max_steps",
19
+ ]
20
+
@@ -0,0 +1,66 @@
1
+ """agent_config.py — Agent config 构建助手
2
+
3
+ 职责:
4
+ 构建 `agent.invoke/ainvoke/astream*` 所需的 config 字典。
5
+ 只负责 LangGraph 配置参数(如 recursion_limit)。
6
+
7
+ Note:
8
+ - run_id 由 HookGraphWiring/TraceLifecycleCollector 注入,不需要手动配置
9
+ - session_id 在 create_loop_agent 时配置,不在此处
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import Any
15
+
16
+
17
+ def build_agent_config(
18
+ *,
19
+ recursion_limit: int = 9_999,
20
+ **kwargs: Any,
21
+ ) -> dict[str, Any]:
22
+ """构建 Agent 运行配置(LangGraph 配置参数)。
23
+
24
+ Args:
25
+ recursion_limit: 运行递归上限(默认 9999)。
26
+ **kwargs: 其他 LangGraph config 参数(如 metadata, configurable 等)。
27
+
28
+ Returns:
29
+ 可直接传给 `agent.invoke(..., config=...)` 的配置字典。
30
+
31
+ Example:
32
+ >>> # 使用默认配置
33
+ >>> config = build_agent_config()
34
+ >>> result = agent.invoke({"messages": [...]}, config=config)
35
+
36
+ >>> # 自定义 recursion_limit
37
+ >>> config = build_agent_config(recursion_limit=5000)
38
+ >>> result = agent.invoke({"messages": [...]}, config=config)
39
+
40
+ >>> # 添加其他 LangGraph 配置
41
+ >>> config = build_agent_config(
42
+ ... recursion_limit=5000,
43
+ ... metadata={"user_id": "123"},
44
+ ... )
45
+ >>> result = agent.invoke({"messages": [...]}, config=config)
46
+
47
+ Note:
48
+ - run_id 由 HookGraphWiring/TraceLifecycleCollector 在每次调用时自动注入
49
+ - session_id 已在 create_loop_agent 时配置
50
+ - 如果不需要自定义配置,可以直接调用 agent.invoke() 不传 config
51
+ """
52
+ config: dict[str, Any] = {
53
+ "recursion_limit": recursion_limit,
54
+ }
55
+
56
+ # 合并其他参数
57
+ if kwargs:
58
+ config.update(kwargs)
59
+
60
+ return config
61
+
62
+
63
+ __all__ = ["build_agent_config"]
64
+
65
+
66
+
@@ -0,0 +1,72 @@
1
+ """agent_loop_config.py — Agent Loop 不变配置(构建时确定)
2
+
3
+ 职责:
4
+ 以强类型 dataclass 承载与 LangGraph 迭代轮次无关的不变配置;目标态下写入
5
+ `config["configurable"]["loop_config"]`,不进入 LangGraph state。
6
+
7
+ 在整体链路中的位置:
8
+ create_loop_agent(及子代理派生)组装本对象并注入 configurable;工具层
9
+ (如 adapter → ToolExecutionContext)从 configurable 读取模型与权限等控制面,
10
+ 替代从 state 猜测 `__loop_model__` 等临时字段。
11
+
12
+ CC 对照:
13
+ 语义上对齐 CC 的 QueryParams(不变入参)与 toolUseContext.options(控制面)
14
+ 的组合:控制面不进 queryLoop State,而通过独立上下文传递。
15
+
16
+ 当前裁剪范围:
17
+ 仅提供 `AgentLoopConfig` 数据结构;与 `LoopRuntimeContext` 合并、工厂派生、
18
+ factory/adapter 接线在后续步骤完成。
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from dataclasses import dataclass, field
24
+ from typing import Any
25
+
26
+ from langchain_agentx.provider.model_profile import ModelProfile, ModelProfileRegistry
27
+
28
+
29
+ @dataclass
30
+ class AgentLoopConfig:
31
+ """Agent Loop 构建时确定的不变配置。
32
+
33
+ 父子代理共用同一类型:RuntimeContextFactory.derive() 返回子代理的 AgentLoopConfig。
34
+ CC 对照:QueryParams(不变入参)+ toolUseContext.options(控制面)。
35
+ """
36
+
37
+ # 模型控制面
38
+ model: Any # BaseChatModel 或 provider-qualified 字符串
39
+ fallback_model: Any | None = None
40
+
41
+ # 执行限制
42
+ max_steps: int | None = None
43
+ max_turns: int | None = None
44
+ max_withhold_retries: int = 1
45
+ token_budget_max_continuations: int = 3
46
+ managed_rules: str | None = None
47
+
48
+ # 工具与权限
49
+ available_tools: list[Any] = field(default_factory=list)
50
+ permission_mode: str = "default"
51
+ permission_resolver: Any | None = None
52
+ workspace_root: str | None = None # 工具层 workspace 根路径,权限检查边界
53
+ agent_home: str = ".langchain_agentx" # Agent 家目录(skills/worktrees/tasks 派生根)
54
+ cwd: str | None = None # 当前工作目录,路径展开基准,可被子代理覆盖(CC 对照:getCwd())
55
+
56
+ # 会话标识
57
+ session_id: str = ""
58
+ conversation_session_id: str = ""
59
+ agent_id: str | None = None
60
+ parent_agent_id: str | None = None
61
+ agent_depth: int = 0
62
+ subagent_type: str | None = None
63
+ # 观测控制面:主 Agent 打开则子 Agent 默认继承打开
64
+ trace_enabled: bool = True
65
+ # 模型能力配置:显式 profile > registry 匹配 > model metadata > 代码兜底
66
+ model_profile: ModelProfile | None = None
67
+ model_profile_registry: ModelProfileRegistry | None = None
68
+ compact_buffer_tokens: int = 13_000
69
+ max_output_tokens_reserved: int = 20_000
70
+
71
+
72
+ __all__ = ["AgentLoopConfig"]
@@ -0,0 +1,105 @@
1
+ """loop/config/model_context_resolver.py — 模型上下文能力解析器。
2
+
3
+ 职责:
4
+ - 解析当前 loop 使用模型的 context_window。
5
+ - 解析当前 loop 使用模型的 max_output_tokens。
6
+
7
+ 链路位置:
8
+ - loop/graph/factory.py:构建期生成动态 CompactionSettings。
9
+ - 后续 P2b:exit/model 消费侧读取 max_output_tokens 上限。
10
+
11
+ 当前裁剪范围:
12
+ - 仅负责模型能力解析,不做 token 估算,不做 compaction 判定。
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import os
18
+ from typing import Any
19
+
20
+ from langchain_agentx.provider.model_profile import ModelProfileRegistry
21
+
22
+ from .agent_loop_config import AgentLoopConfig
23
+
24
+
25
+ class ModelContextResolver:
26
+ """按约定优先级链解析模型上下文能力。"""
27
+
28
+ def resolve_context_window(self, loop_config: AgentLoopConfig) -> int:
29
+ env_override = self._parse_positive_int(os.getenv("AGENTX_MAX_CONTEXT_TOKENS"))
30
+ if env_override is not None:
31
+ return env_override
32
+
33
+ if loop_config.model_profile is not None:
34
+ return int(loop_config.model_profile.context_window)
35
+
36
+ registry = loop_config.model_profile_registry
37
+ model_id = self._extract_model_id(loop_config.model)
38
+ if registry is not None and model_id:
39
+ profile = registry.get(model_id)
40
+ if profile.model_id_prefix != "__default__":
41
+ return int(profile.context_window)
42
+ if registry.defaults is not None:
43
+ return int(registry.defaults.context_window)
44
+
45
+ metadata_context_window = self._resolve_model_metadata_context_window(loop_config.model)
46
+ if metadata_context_window is not None:
47
+ return metadata_context_window
48
+
49
+ return 32_000
50
+
51
+ def resolve_max_output_tokens(self, loop_config: AgentLoopConfig) -> int:
52
+ if loop_config.model_profile is not None:
53
+ return int(loop_config.model_profile.max_output_tokens)
54
+
55
+ registry = loop_config.model_profile_registry
56
+ model_id = self._extract_model_id(loop_config.model)
57
+ if registry is not None and model_id:
58
+ profile = registry.get(model_id)
59
+ if profile.model_id_prefix != "__default__":
60
+ return int(profile.max_output_tokens)
61
+ if registry.defaults is not None:
62
+ return int(registry.defaults.max_output_tokens)
63
+
64
+ metadata_max_output = self._resolve_model_metadata_max_output_tokens(loop_config.model)
65
+ if metadata_max_output is not None:
66
+ return metadata_max_output
67
+
68
+ return 8_192
69
+
70
+ @staticmethod
71
+ def _extract_model_id(model: Any) -> str | None:
72
+ if isinstance(model, str):
73
+ return model.split(":")[-1] if ":" in model else model
74
+ return getattr(model, "model_name", None) or getattr(model, "model", None)
75
+
76
+ @staticmethod
77
+ def _resolve_model_metadata_context_window(model: Any) -> int | None:
78
+ metadata = getattr(model, "metadata", None) or {}
79
+ if not isinstance(metadata, dict):
80
+ return None
81
+ return ModelContextResolver._parse_positive_int(metadata.get("context_window"))
82
+
83
+ @staticmethod
84
+ def _resolve_model_metadata_max_output_tokens(model: Any) -> int | None:
85
+ metadata = getattr(model, "metadata", None) or {}
86
+ if not isinstance(metadata, dict):
87
+ return None
88
+ for key in ("max_output_tokens", "max_tokens"):
89
+ parsed = ModelContextResolver._parse_positive_int(metadata.get(key))
90
+ if parsed is not None:
91
+ return parsed
92
+ return None
93
+
94
+ @staticmethod
95
+ def _parse_positive_int(value: Any) -> int | None:
96
+ if value is None:
97
+ return None
98
+ try:
99
+ parsed = int(str(value).strip())
100
+ except (TypeError, ValueError):
101
+ return None
102
+ return parsed if parsed > 0 else None
103
+
104
+
105
+ __all__ = ["ModelContextResolver"]
@@ -0,0 +1,50 @@
1
+ """runtime_settings.py — Loop 运行时全局策略设置。
2
+
3
+ 职责:
4
+ 提供 max_steps 的透明解析函数。
5
+ 接口语义与实现语义一致:None = 不限制,int = 确切上限。
6
+
7
+ 设计变更(2026-05-01):
8
+ 移除 get_global_max_steps_default() — 不再通过环境变量隐藏默认值 120。
9
+ 接口设置 max_steps=None 就是真正的不限制,没有静默兜底。
10
+ 环境变量支持由调用方(create_loop_agent 入口)自行处理,不做隐式注入。
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ UNBOUNDED_MAX_STEPS = 1_000_000
16
+
17
+
18
+ def resolve_max_steps(value: int | None) -> int | None:
19
+ """透明解析:None → None(不限),int → int(确切上限)。
20
+
21
+ 与接口语义保持一致,不做隐式默认值注入。
22
+ """
23
+ return value
24
+
25
+
26
+ def materialize_max_steps(value: int | None) -> int:
27
+ """将可选 max_steps 物化为 Loop 内部可执行上限。
28
+
29
+ None(不限步)→ UNBOUNDED_MAX_STEPS(1_000_000),
30
+ int → 原值。
31
+ """
32
+ return UNBOUNDED_MAX_STEPS if value is None else value
33
+
34
+
35
+ def compute_recursion_limit(value: int | None) -> int:
36
+ """按 OpenCode 公式计算 recursion_limit。
37
+
38
+ recursion_limit 是 LangGraph 图层面的安全阀,
39
+ 与 max_steps 的退出逻辑是两层不同的守卫机制。
40
+ """
41
+ effective_steps = materialize_max_steps(value)
42
+ return 2 * effective_steps + 100
43
+
44
+
45
+ __all__ = [
46
+ "UNBOUNDED_MAX_STEPS",
47
+ "resolve_max_steps",
48
+ "materialize_max_steps",
49
+ "compute_recursion_limit",
50
+ ]
@@ -0,0 +1,133 @@
1
+ """loop/config/token_estimator.py — token 估算协作者。
2
+
3
+ 职责:
4
+ - 优先使用消息中的真实 usage 元数据估算 token。
5
+ - 无真实 usage 时回退到字符近似估算。
6
+
7
+ 链路位置:
8
+ - memory/session/session_memory.py:决定是否触发 session memory 提取。
9
+ - 后续 P2b:可被更多 loop 消费侧复用。
10
+
11
+ 当前裁剪范围:
12
+ - 仅实现消息级 token 估算;不负责 context window 判定。
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from collections.abc import Mapping
18
+ from typing import Any
19
+
20
+ class TokenEstimator:
21
+ """真实 usage 优先,字符近似兜底。"""
22
+
23
+ def __init__(self, *, num_chars_per_token: int = 4) -> None:
24
+ self._num_chars_per_token = num_chars_per_token
25
+
26
+ def estimate_messages_tokens(self, messages: list[Any]) -> int:
27
+ baseline_tokens: int | None = None
28
+ baseline_index = -1
29
+
30
+ for index in range(len(messages) - 1, -1, -1):
31
+ usage_tokens = self.extract_usage_tokens(messages[index])
32
+ if usage_tokens is not None:
33
+ baseline_tokens = usage_tokens
34
+ baseline_index = index
35
+ break
36
+
37
+ if baseline_tokens is None:
38
+ return self._estimate_chars_tokens(messages)
39
+
40
+ return baseline_tokens + self._estimate_chars_tokens(messages[baseline_index + 1 :])
41
+
42
+ def extract_usage_tokens(self, message: Any) -> int | None:
43
+ usage = self._extract_usage_payload(message)
44
+ if not usage:
45
+ return None
46
+
47
+ if "input_tokens" in usage:
48
+ return (
49
+ self._as_int(usage.get("input_tokens"))
50
+ + self._as_int(usage.get("cache_creation_input_tokens"))
51
+ + self._as_int(usage.get("cache_read_input_tokens"))
52
+ + self._as_int(usage.get("output_tokens"))
53
+ )
54
+
55
+ if "prompt_tokens" in usage or "completion_tokens" in usage:
56
+ return self._as_int(usage.get("prompt_tokens")) + self._as_int(
57
+ usage.get("completion_tokens")
58
+ )
59
+
60
+ if "total_tokens" in usage:
61
+ total_tokens = self._as_int(usage.get("total_tokens"))
62
+ return total_tokens if total_tokens > 0 else None
63
+
64
+ return None
65
+
66
+ def _estimate_chars_tokens(self, messages: list[Any]) -> int:
67
+ total_chars = 0
68
+ for message in messages:
69
+ total_chars += len(self._stringify_content(self._extract_content(message)))
70
+ return self._estimate_tokens_from_chars(total_chars)
71
+
72
+ @staticmethod
73
+ def _extract_usage_payload(message: Any) -> Mapping[str, Any] | None:
74
+ if isinstance(message, Mapping):
75
+ response_metadata = message.get("response_metadata")
76
+ if isinstance(response_metadata, Mapping):
77
+ usage = response_metadata.get("usage") or response_metadata.get("token_usage")
78
+ return usage if isinstance(usage, Mapping) else None
79
+ usage = message.get("usage") or message.get("token_usage")
80
+ return usage if isinstance(usage, Mapping) else None
81
+
82
+ response_metadata = getattr(message, "response_metadata", None)
83
+ if isinstance(response_metadata, Mapping):
84
+ usage = response_metadata.get("usage") or response_metadata.get("token_usage")
85
+ if isinstance(usage, Mapping):
86
+ return usage
87
+
88
+ usage_metadata = getattr(message, "usage_metadata", None)
89
+ if isinstance(usage_metadata, Mapping):
90
+ return usage_metadata
91
+ return None
92
+
93
+ @staticmethod
94
+ def _extract_content(message: Any) -> Any:
95
+ if isinstance(message, Mapping):
96
+ return message.get("content", "")
97
+ return getattr(message, "content", "")
98
+
99
+ def _estimate_tokens_from_chars(self, n_chars: int) -> int:
100
+ if self._num_chars_per_token <= 0:
101
+ return n_chars
102
+ return (max(0, n_chars) + self._num_chars_per_token - 1) // self._num_chars_per_token
103
+
104
+ @staticmethod
105
+ def _stringify_content(content: Any) -> str:
106
+ if content is None:
107
+ return ""
108
+ if isinstance(content, str):
109
+ return content
110
+ if isinstance(content, list):
111
+ parts: list[str] = []
112
+ for block in content:
113
+ if isinstance(block, str):
114
+ parts.append(block)
115
+ elif isinstance(block, dict):
116
+ if block.get("type") == "text":
117
+ parts.append(str(block.get("text", "")))
118
+ else:
119
+ parts.append(str(block))
120
+ else:
121
+ parts.append(str(block))
122
+ return "".join(parts)
123
+ return str(content)
124
+
125
+ @staticmethod
126
+ def _as_int(value: Any) -> int:
127
+ try:
128
+ return int(value)
129
+ except (TypeError, ValueError):
130
+ return 0
131
+
132
+
133
+ __all__ = ["TokenEstimator"]
@@ -0,0 +1,66 @@
1
+ """主循环上下文治理(OOP 门面 + CC 五段 `stages/`)。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .blocking_guard import CompactionBlockingGuard, default_compaction_blocking_guard
6
+ from .compaction_service import LoopContextCompaction, default_loop_context_compaction
7
+ from .pipeline import ContextPipeline, DefaultCompactionStages
8
+ from .settings import (
9
+ AutocompactLlmPromptKind,
10
+ CompactionSettings,
11
+ active_compaction_settings,
12
+ get_active_compaction_settings,
13
+ set_active_compaction_settings,
14
+ )
15
+ from .stages import (
16
+ AutocompactStage,
17
+ ContextCollapseStage,
18
+ MicrocompactStage,
19
+ SnipCompactStage,
20
+ ToolResultBudgetStage,
21
+ )
22
+ from .types import (
23
+ CompactionConfig,
24
+ ContextCompactionContext,
25
+ StageResult,
26
+ TokenWarningLevel,
27
+ TokenWarningState,
28
+ )
29
+
30
+ # 压缩提示词函数从 loop.prompt.compact 导入(向后兼容)
31
+ from ..prompt.compact import (
32
+ build_autocompact_llm_instruction_prompt,
33
+ format_compact_summary,
34
+ get_compact_prompt,
35
+ get_compact_user_summary_message,
36
+ get_partial_compact_prompt,
37
+ )
38
+
39
+ __all__ = [
40
+ "active_compaction_settings",
41
+ "AutocompactLlmPromptKind",
42
+ "AutocompactStage",
43
+ "CompactionBlockingGuard",
44
+ "CompactionConfig",
45
+ "CompactionSettings",
46
+ "ContextCollapseStage",
47
+ "ContextCompactionContext",
48
+ "ContextPipeline",
49
+ "DefaultCompactionStages",
50
+ "default_compaction_blocking_guard",
51
+ "default_loop_context_compaction",
52
+ "build_autocompact_llm_instruction_prompt",
53
+ "format_compact_summary",
54
+ "get_active_compaction_settings",
55
+ "get_compact_prompt",
56
+ "get_compact_user_summary_message",
57
+ "get_partial_compact_prompt",
58
+ "LoopContextCompaction",
59
+ "MicrocompactStage",
60
+ "set_active_compaction_settings",
61
+ "SnipCompactStage",
62
+ "StageResult",
63
+ "TokenWarningLevel",
64
+ "TokenWarningState",
65
+ "ToolResultBudgetStage",
66
+ ]
@@ -0,0 +1,97 @@
1
+ """
2
+ 紧凑化路径上的 token 告警与「是否拦二次模型」守卫(阶段 C)。
3
+
4
+ 与 Claude Code 对齐:
5
+ - `calculate_token_warning_state` 分档:`ok` / `warn` / `block_compact_llm` / `block_main_model`,
6
+ 语义上贴近 CC `calculateTokenWarningState` 与 compact 前「过满则勿再调摘要模型」;
7
+ - `should_block`:是否建议 **拦主循环 model**(估计已超过上下文硬顶),供 Graph / middleware 协作。
8
+
9
+ 设计要点:
10
+ - 估计使用 `message_utils.total_estimated_tokens_for_messages`,与五段一致;
11
+ - **不**插入默认五段列表;`AutocompactStage` 在调用 compact LLM 前查询 `should_block_compact_llm`。
12
+
13
+ 注意:
14
+ - 不修改 LangGraph state;与 `CompactionStage.run` 正交。
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from typing import Any
20
+
21
+ from ..config.token_estimator import TokenEstimator
22
+ from .settings import CompactionSettings, get_active_compaction_settings
23
+ from .types import ContextCompactionContext, TokenWarningLevel, TokenWarningState
24
+
25
+
26
+ class CompactionBlockingGuard:
27
+ """基于启发式 token 的告警与阻塞建议。"""
28
+
29
+ def __init__(self, settings: CompactionSettings | None = None) -> None:
30
+ self._settings_override = settings
31
+ self._token_estimator = TokenEstimator(
32
+ num_chars_per_token=(settings.num_chars_per_token if settings is not None else 4)
33
+ )
34
+
35
+ def _settings(self) -> CompactionSettings:
36
+ return self._settings_override or get_active_compaction_settings()
37
+
38
+ def estimate_messages_tokens(self, messages: list[Any]) -> int:
39
+ return self._token_estimator.estimate_messages_tokens(messages)
40
+
41
+ def calculate_token_warning_state(self, messages: list[Any]) -> TokenWarningState:
42
+ s = self._settings()
43
+ est = self.estimate_messages_tokens(messages)
44
+ max_ctx = max(1, s.default_max_context_tokens)
45
+ warn_line = int(max_ctx * s.compaction_token_warn_fraction)
46
+ block_llm_line = int(max_ctx * s.compaction_block_compact_llm_fraction)
47
+ block_main_line = int(max_ctx * s.compaction_block_main_model_fraction)
48
+ level: TokenWarningLevel
49
+ if est >= block_main_line:
50
+ level = "block_main_model"
51
+ elif est >= block_llm_line:
52
+ level = "block_compact_llm"
53
+ elif est >= warn_line:
54
+ level = "warn"
55
+ else:
56
+ level = "ok"
57
+ return TokenWarningState(
58
+ estimated_tokens=est,
59
+ warn_threshold_tokens=warn_line,
60
+ block_compact_llm_threshold_tokens=block_llm_line,
61
+ block_main_model_threshold_tokens=block_main_line,
62
+ level=level,
63
+ )
64
+
65
+ def should_block_compact_llm(self, messages: list[Any]) -> bool:
66
+ """估计已超过「compact 二次 LLM」安全线时返回 True(仅走 prune / 跳过摘要模型)。"""
67
+ st = self.calculate_token_warning_state(messages)
68
+ return st.level in ("block_compact_llm", "block_main_model")
69
+
70
+ def should_block(
71
+ self,
72
+ ctx: ContextCompactionContext,
73
+ *,
74
+ estimated_tokens: int = 0,
75
+ ) -> bool:
76
+ """是否建议阻塞 **主** model(由调用方传入已算好的 token 或自行先算 messages)。"""
77
+ _ = ctx
78
+ s = self._settings()
79
+ line = int(s.default_max_context_tokens * s.compaction_block_main_model_fraction)
80
+ return estimated_tokens >= line
81
+
82
+
83
+ _default_guard: CompactionBlockingGuard | None = None
84
+
85
+
86
+ def default_compaction_blocking_guard() -> CompactionBlockingGuard:
87
+ """进程内单例守卫。"""
88
+ global _default_guard
89
+ if _default_guard is None:
90
+ _default_guard = CompactionBlockingGuard()
91
+ return _default_guard
92
+
93
+
94
+ __all__ = [
95
+ "CompactionBlockingGuard",
96
+ "default_compaction_blocking_guard",
97
+ ]