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,156 @@
1
+ """provider/model_profile.py — 模型能力参数配置对象与注册表。
2
+
3
+ 职责:
4
+ - ModelProfile:持有单个模型的能力参数(context_window / max_output_tokens / 默认采样参数等)
5
+ - ModelProfileRegistry:从「内置 + 外部」两层 yaml 加载,并按 model_id_prefix 做最长前缀匹配
6
+
7
+ 链路位置:
8
+ - provider/openai.py、provider/anthropic.py:读取默认采样参数与输出上限(P1 接入)
9
+ - loop/config/model_context_resolver.py:解析 context_window(P2 接入)
10
+
11
+ 当前裁剪范围:
12
+ - 仅负责参数加载与匹配;不负责 loop 业务判断、不负责 env/.env 加载、不做运行时热重载。
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ from dataclasses import dataclass, field
19
+ from pathlib import Path
20
+ from typing import Any, ClassVar
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ def _builtin_profiles_path() -> Path:
26
+ # langchain_agentx/provider/model_profile.py -> langchain_agentx/config/model_profiles.yaml
27
+ return Path(__file__).resolve().parents[1] / "config" / "model_profiles.yaml"
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class ModelProfile:
32
+ """单个模型的能力参数(纯数据持有,无业务逻辑)。"""
33
+
34
+ model_id_prefix: str
35
+ context_window: int
36
+ max_output_tokens: int
37
+ provider: str # "claude" | "openai"
38
+ supports_cache: bool = False
39
+ default_temperature: float = 0.7
40
+ default_top_p: float | None = None
41
+
42
+ PROVIDERS: ClassVar[frozenset[str]] = frozenset({"claude", "openai"})
43
+
44
+ def __post_init__(self) -> None:
45
+ if self.provider not in self.PROVIDERS:
46
+ msg = f"invalid provider={self.provider!r}, must be one of {sorted(self.PROVIDERS)}"
47
+ raise ValueError(msg)
48
+ if self.context_window <= 0 or self.max_output_tokens <= 0:
49
+ raise ValueError("context_window/max_output_tokens must be positive")
50
+ if not self.model_id_prefix or not str(self.model_id_prefix).strip():
51
+ raise ValueError("model_id_prefix must be non-empty")
52
+
53
+
54
+ @dataclass
55
+ class ModelProfileRegistry:
56
+ """两层 yaml 加载 + model_id_prefix 最长前缀优先匹配。"""
57
+
58
+ profiles: dict[str, ModelProfile] = field(default_factory=dict)
59
+ defaults: ModelProfile = field(
60
+ default_factory=lambda: ModelProfile(
61
+ model_id_prefix="__default__",
62
+ context_window=32_000,
63
+ max_output_tokens=8_192,
64
+ provider="openai",
65
+ supports_cache=False,
66
+ default_temperature=0.7,
67
+ default_top_p=0.5,
68
+ )
69
+ )
70
+
71
+ @classmethod
72
+ def from_files(cls, *, agent_home_dir: Path | None = None) -> "ModelProfileRegistry":
73
+ """从内置配置与可选外部配置构造一个独立 registry。"""
74
+ registry = cls()
75
+ registry.load(agent_home_dir=agent_home_dir)
76
+ return registry
77
+
78
+ def load(self, *, agent_home_dir: Path | None = None) -> None:
79
+ """加载内置 yaml,再用外部 yaml 覆盖。"""
80
+ try:
81
+ import yaml # type: ignore[import-not-found]
82
+ except Exception:
83
+ # P0:缺依赖时回退 defaults;P1/P2 的接线会依赖此处输出可观测 warning
84
+ logger.warning("pyyaml 未安装,跳过 model_profiles.yaml 加载,使用 defaults 兜底")
85
+ return
86
+
87
+ builtin_path = _builtin_profiles_path()
88
+ builtin_profiles, builtin_defaults = self._parse_yaml(yaml, builtin_path)
89
+
90
+ external_profiles: dict[str, ModelProfile] = {}
91
+ external_defaults: ModelProfile | None = None
92
+ if agent_home_dir is not None:
93
+ ext_path = agent_home_dir / "provider" / "model_profiles.yaml"
94
+ if ext_path.exists():
95
+ external_profiles, external_defaults = self._parse_yaml(yaml, ext_path)
96
+ logger.debug("已加载外部模型配置: %s (%d 条)", ext_path, len(external_profiles))
97
+
98
+ merged = {**builtin_profiles, **external_profiles}
99
+ self.defaults = external_defaults or builtin_defaults or self.defaults
100
+ self.profiles = merged
101
+
102
+ def get(self, model_id: str) -> ModelProfile:
103
+ """按 model_id_prefix 做最长前缀优先匹配;未命中返回 defaults。"""
104
+ if not model_id:
105
+ return self.defaults
106
+ matched: ModelProfile | None = None
107
+ for p in self.profiles.values():
108
+ if model_id.startswith(p.model_id_prefix):
109
+ if matched is None or len(p.model_id_prefix) > len(matched.model_id_prefix):
110
+ matched = p
111
+ return matched or self.defaults
112
+
113
+ def _parse_yaml(
114
+ self, yaml_mod: Any, path: Path
115
+ ) -> tuple[dict[str, ModelProfile], ModelProfile | None]:
116
+ try:
117
+ raw = yaml_mod.safe_load(path.read_text(encoding="utf-8")) or {}
118
+ except Exception as e:
119
+ logger.warning("model_profiles.yaml 解析失败 %s: %s", path, e)
120
+ return {}, None
121
+
122
+ defaults_profile: ModelProfile | None = None
123
+ if isinstance(raw, dict) and raw.get("defaults"):
124
+ defaults_profile = self._parse_profile({"model_id_prefix": "__default__", **raw["defaults"]})
125
+
126
+ result: dict[str, ModelProfile] = {}
127
+ for item in (raw.get("profiles") or []) if isinstance(raw, dict) else []:
128
+ if not isinstance(item, dict):
129
+ continue
130
+ try:
131
+ p = self._parse_profile(item)
132
+ except Exception as e:
133
+ logger.warning("跳过无效 profile 条目 %s: %s", item, e)
134
+ continue
135
+ result[p.model_id_prefix] = p
136
+
137
+ return result, defaults_profile
138
+
139
+ @staticmethod
140
+ def _parse_profile(item: dict[str, Any]) -> ModelProfile:
141
+ return ModelProfile(
142
+ model_id_prefix=str(item["model_id_prefix"]),
143
+ context_window=int(item["context_window"]),
144
+ max_output_tokens=int(item["max_output_tokens"]),
145
+ provider=str(item.get("provider", "openai")),
146
+ supports_cache=bool(item.get("supports_cache", False)),
147
+ default_temperature=float(item.get("default_temperature", 0.7)),
148
+ default_top_p=item.get("default_top_p", None),
149
+ )
150
+
151
+
152
+ __all__ = [
153
+ "ModelProfile",
154
+ "ModelProfileRegistry",
155
+ ]
156
+
@@ -0,0 +1,89 @@
1
+ """provider/openai.py — OpenAI 兼容 ChatModel 工厂(可复用组合件)。
2
+
3
+ 职责:
4
+ - 从环境变量读取 OpenAI 兼容服务配置(key/base_url/model)。
5
+ - 组装并返回 CompatibleChatOpenAI。
6
+
7
+ 约束:
8
+ - 不加载 .env;由调用方(例如 examples)负责把 .env 注入到环境变量。
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ from typing import Any
15
+
16
+ from langchain_core.language_models.chat_models import BaseChatModel
17
+
18
+ from .compatible_chat_openai import CompatibleChatOpenAI
19
+ from .env import get_env_first
20
+ from .model_profile import ModelProfileRegistry
21
+
22
+
23
+ def _sanitize_proxy_env_for_openai_compat() -> None:
24
+ """部分 http 客户端不接受 socks:// 代理,移除异常 all_proxy。"""
25
+ for key in ("all_proxy", "ALL_PROXY"):
26
+ value = os.environ.get(key)
27
+ if not value:
28
+ continue
29
+ if value.strip().lower().startswith("socks://"):
30
+ os.environ.pop(key, None)
31
+
32
+
33
+ def get_chat_openai(
34
+ *,
35
+ model: str | None = None,
36
+ api_key: str | None = None,
37
+ base_url: str | None = None,
38
+ temperature: float | None = None,
39
+ max_tokens: int | None = None,
40
+ top_p: float | None = None,
41
+ streaming: bool = True,
42
+ model_profile_registry: ModelProfileRegistry | None = None,
43
+ **kwargs: Any,
44
+ ) -> BaseChatModel:
45
+ _sanitize_proxy_env_for_openai_compat()
46
+
47
+ resolved_api_key = api_key or get_env_first(
48
+ "LANGCHAIN_AGENTX_OPENAI_API_KEY",
49
+ "OPENAI_API_KEY",
50
+ )
51
+ resolved_base_url = base_url or get_env_first(
52
+ "LANGCHAIN_AGENTX_OPENAI_BASE_URL",
53
+ "OPENAI_BASE_URL",
54
+ )
55
+ resolved_model = model or get_env_first("LANGCHAIN_AGENTX_MODEL") or "gpt-4o-mini"
56
+ profile = (
57
+ model_profile_registry.get(resolved_model)
58
+ if model_profile_registry is not None
59
+ else ModelProfileRegistry().defaults
60
+ )
61
+
62
+ if not resolved_api_key or not resolved_base_url:
63
+ raise RuntimeError(
64
+ "缺少 OpenAI 兼容端点配置:请设置 OPENAI_API_KEY(或 LANGCHAIN_AGENTX_OPENAI_API_KEY)"
65
+ " 以及 OPENAI_BASE_URL(或 LANGCHAIN_AGENTX_OPENAI_BASE_URL)。"
66
+ " 默认模型名可通过 LANGCHAIN_AGENTX_MODEL 或 model 参数指定。"
67
+ )
68
+
69
+ resolved_temperature = profile.default_temperature if temperature is None else float(temperature)
70
+ resolved_max_tokens = profile.max_output_tokens if max_tokens is None else int(max_tokens)
71
+ resolved_top_p = profile.default_top_p if top_p is None else float(top_p)
72
+
73
+ init_kwargs: dict[str, Any] = {
74
+ "model": resolved_model,
75
+ "api_key": resolved_api_key,
76
+ "base_url": resolved_base_url.rstrip("/"),
77
+ "temperature": resolved_temperature,
78
+ "max_tokens": resolved_max_tokens,
79
+ "streaming": streaming,
80
+ **kwargs,
81
+ }
82
+ if resolved_top_p is not None:
83
+ init_kwargs["top_p"] = resolved_top_p
84
+
85
+ return CompatibleChatOpenAI(**init_kwargs)
86
+
87
+
88
+ __all__ = ["get_chat_openai"]
89
+
@@ -0,0 +1,17 @@
1
+ """Session 容器导出。"""
2
+
3
+ from .agent_session import AgentSession
4
+ from .conversation_recovery import load_session_messages
5
+ from .conversation_factory import create_conversation_session
6
+ from .conversation_session import ConversationSession
7
+ from .factory import create_agent_session
8
+ from .protocol import LoopContainer
9
+
10
+ __all__ = [
11
+ "AgentSession",
12
+ "ConversationSession",
13
+ "LoopContainer",
14
+ "create_agent_session",
15
+ "create_conversation_session",
16
+ "load_session_messages",
17
+ ]
@@ -0,0 +1,320 @@
1
+ """
2
+ session/agent_session.py — 进程级 Session 生命周期容器。
3
+
4
+ 职责:
5
+ 负责触发进程级 SESSION_START/SESSION_END,封装多次 loop invoke,并维护跨 loop 状态。
6
+
7
+ 链路位置:
8
+ 作为 Agent Loop 外层容器供 CLI/应用复用,对应 A01/A03 约定的 LoopContainer 协议。
9
+
10
+ 当前裁剪范围:
11
+ 当前实现覆盖最小生命周期与 active_files 累积;memory manager 通过注入方式可选接入。
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import inspect
17
+ import logging
18
+ import time
19
+ from uuid import uuid4
20
+ from typing import Any, AsyncIterator, Callable
21
+
22
+ from langchain_core.messages import HumanMessage
23
+ from langgraph.graph.state import CompiledStateGraph
24
+
25
+ from ..command import CommandContext, CommandDispatcher, CommandRegistry
26
+ from ..command.builtin import (
27
+ make_clear_command,
28
+ make_compact_command,
29
+ make_memory_command,
30
+ make_reload_plugins_command,
31
+ )
32
+ from ..loop.context.compaction_service import default_loop_context_compaction
33
+ from ..loop.context.message_utils import total_estimated_tokens_for_messages
34
+ from ..loop.hook.types import HookContext, HookEvent
35
+ from ..memory.memdir.agent_memory import drain_pending_agent_memory_extraction
36
+ from ..memory.memdir.extractor import drain_pending_extraction
37
+ from ..plugin.loader import PluginLoader
38
+ from ..plugin.registries import AgentRegistry, CommandRegistry as PluginCommandRegistry, SkillRegistry
39
+ from ..plugin.types import PluginLoadResult
40
+ from ..loop.subagent.transcript import SubagentTranscriptStore
41
+ from ..workspace.config import AgentWorkspaceConfig
42
+
43
+ logger = logging.getLogger(__name__)
44
+
45
+
46
+ class AgentSession:
47
+ """进程级 session 容器。"""
48
+
49
+ def __init__(
50
+ self,
51
+ graph: CompiledStateGraph | None,
52
+ session_id: str,
53
+ workspace_cfg: AgentWorkspaceConfig,
54
+ hook_engine: Any,
55
+ *,
56
+ graph_factory: Callable[..., CompiledStateGraph] | None = None,
57
+ plugin_loader: PluginLoader | None = None,
58
+ transcript_writer: SubagentTranscriptStore | None = None,
59
+ container_type: str = "interactive",
60
+ enable_trace: bool = True,
61
+ ) -> None:
62
+ self._graph = graph
63
+ self._graph_factory = graph_factory
64
+ self._session_id = session_id
65
+ self._workspace_cfg = workspace_cfg
66
+ self._hook_engine = hook_engine
67
+ self._active_files: list[str] = []
68
+ self._messages: list[Any] = []
69
+ self._container_type = container_type
70
+ self._enable_trace = enable_trace
71
+ self._transcript_writer = transcript_writer
72
+ self._last_transcript_uuid: str | None = None
73
+ self._session_memory_manager: Any | None = None
74
+ self._skill_registry = SkillRegistry()
75
+ self._command_registry = PluginCommandRegistry()
76
+ self._agent_registry = AgentRegistry()
77
+ self._runtime_command_registry = CommandRegistry()
78
+ self._register_builtin_commands()
79
+ self._dispatcher = CommandDispatcher(
80
+ self._runtime_command_registry,
81
+ plugin_command_registry=self._command_registry,
82
+ )
83
+ self._plugin_loader: PluginLoader | None
84
+ if not workspace_cfg.plugins_enabled:
85
+ self._plugin_loader = None
86
+ else:
87
+ self._plugin_loader = plugin_loader or PluginLoader(
88
+ workspace_config=workspace_cfg,
89
+ hook_engine=hook_engine,
90
+ skill_registry=self._skill_registry,
91
+ command_registry=self._command_registry,
92
+ agent_registry=self._agent_registry,
93
+ )
94
+
95
+ async def __aenter__(self) -> "AgentSession":
96
+ if self._graph is None:
97
+ self._graph = self._build_graph()
98
+ if self._plugin_loader is not None:
99
+ load_result = await self._plugin_loader.load_all(container_type="interactive")
100
+ load_result.log_errors(logger)
101
+ await self._execute_hook(HookEvent.SESSION_START)
102
+ return self
103
+
104
+ async def __aexit__(self, *_) -> None:
105
+ if self._session_memory_manager is not None:
106
+ await self._session_memory_manager.drain(timeout=30.0)
107
+ await drain_pending_extraction(timeout_ms=60_000)
108
+ await drain_pending_agent_memory_extraction(timeout_ms=60_000)
109
+ await self._execute_hook(HookEvent.SESSION_END)
110
+ if self._plugin_loader is not None:
111
+ await self._plugin_loader.unload_all()
112
+
113
+ async def run_loop(self, user_input: str) -> dict[str, Any]:
114
+ if self._graph is None:
115
+ raise RuntimeError("AgentSession graph is not initialized")
116
+ appended_message = HumanMessage(content=user_input)
117
+ self._messages.append(appended_message)
118
+ baseline_count = len(self._messages)
119
+ try:
120
+ result = await self._graph.ainvoke(
121
+ {
122
+ "messages": list(self._messages),
123
+ "_session_id": self._session_id,
124
+ }
125
+ )
126
+ except Exception:
127
+ # 回滚本轮 append,避免异常路径重复累积 user 消息。
128
+ if self._messages and self._messages[-1] is appended_message:
129
+ self._messages.pop()
130
+ raise
131
+ if isinstance(result, dict) and isinstance(result.get("messages"), list):
132
+ new_messages = list(result["messages"])
133
+ self._messages = new_messages
134
+ self._append_transcript_message(appended_message)
135
+ for message in new_messages[baseline_count:]:
136
+ self._append_transcript_message(message)
137
+ self._active_files.extend(self._extract_accessed_files(result))
138
+ return dict(result)
139
+
140
+ async def stream_loop_events(
141
+ self,
142
+ user_input: str,
143
+ *,
144
+ config: dict[str, Any] | None = None,
145
+ ) -> AsyncIterator[dict[str, Any]]:
146
+ """
147
+ 流式执行一轮 loop,复用 AgentSession 的消息上下文。
148
+
149
+ 说明:
150
+ 该方法用于需要 astream_events 的场景(如 SSE),执行完成后会尝试
151
+ 从 on_chain_end(LangGraph) 事件回填消息列表,保持与 run_loop 一致的上下文累积语义。
152
+ """
153
+ if self._graph is None:
154
+ raise RuntimeError("AgentSession graph is not initialized")
155
+ appended_message = HumanMessage(content=user_input)
156
+ self._messages.append(appended_message)
157
+ baseline_count = len(self._messages)
158
+ output_messages: list[Any] | None = None
159
+ try:
160
+ async for event in self._graph.astream_events(
161
+ {
162
+ "messages": list(self._messages),
163
+ "_session_id": self._session_id,
164
+ },
165
+ config=config,
166
+ version="v2",
167
+ ):
168
+ if event.get("event") == "on_chain_end" and event.get("name") == "LangGraph":
169
+ data = event.get("data", {}) or {}
170
+ output = data.get("output")
171
+ if isinstance(output, dict) and isinstance(output.get("messages"), list):
172
+ output_messages = list(output["messages"])
173
+ yield event
174
+ except Exception:
175
+ if self._messages and self._messages[-1] is appended_message:
176
+ self._messages.pop()
177
+ raise
178
+ if output_messages is not None:
179
+ self._messages = output_messages
180
+ self._append_transcript_message(appended_message)
181
+ for message in output_messages[baseline_count:]:
182
+ self._append_transcript_message(message)
183
+
184
+ @property
185
+ def active_files(self) -> list[str]:
186
+ return list(self._active_files)
187
+
188
+ @property
189
+ def dispatcher(self) -> CommandDispatcher:
190
+ return self._dispatcher
191
+
192
+ def clear_context(self) -> None:
193
+ self._messages.clear()
194
+
195
+ async def compact_context(self) -> tuple[int, int]:
196
+ before_tokens = total_estimated_tokens_for_messages(self._messages, num_chars_per_token=4)
197
+ patch = default_loop_context_compaction().run({"messages": self._messages})
198
+ if isinstance(patch.get("messages"), list):
199
+ self._messages = list(patch["messages"])
200
+ after_tokens = total_estimated_tokens_for_messages(self._messages, num_chars_per_token=4)
201
+ if self._transcript_writer is not None:
202
+ self._append_compact_boundary(before_tokens=before_tokens, after_tokens=after_tokens)
203
+ return before_tokens, after_tokens
204
+
205
+ async def reload_plugins(self) -> PluginLoadResult:
206
+ if self._plugin_loader is None:
207
+ return PluginLoadResult(enabled=[], errors=[])
208
+ result = await self._plugin_loader.reload(container_type=self._container_type)
209
+ result.log_errors(logger)
210
+ return result
211
+
212
+ def get_memory_summary(self, sub_cmd: str = "show") -> str:
213
+ memory_path = self._workspace_cfg.session_memory_path(self._session_id)
214
+ if sub_cmd == "clear":
215
+ if memory_path.exists():
216
+ memory_path.unlink()
217
+ return "记忆已清除"
218
+ if not memory_path.exists():
219
+ return "(暂无记忆摘要)"
220
+ content = memory_path.read_text(encoding="utf-8").strip()
221
+ return content or "(暂无记忆摘要)"
222
+
223
+ def _build_context(self) -> CommandContext:
224
+ return CommandContext(
225
+ session_id=self._session_id,
226
+ session=self,
227
+ workspace_cfg=self._workspace_cfg,
228
+ hook_engine=self._hook_engine,
229
+ plugin_loader=self._plugin_loader,
230
+ extra={"container_type": self._container_type},
231
+ )
232
+
233
+ async def dispatch_command(self, user_input: str):
234
+ return await self._dispatcher.dispatch(user_input, self._build_context())
235
+
236
+ def list_commands(self) -> list[dict[str, object]]:
237
+ return self._runtime_command_registry.list_for_help()
238
+
239
+ async def _execute_hook(self, event: HookEvent) -> None:
240
+ ctx = HookContext(event=event, state={}, session_id=self._session_id)
241
+ # HookEngine 当前主接口是 execute;若未来扩展 execute_async,优先兼容新接口。
242
+ execute_async = getattr(self._hook_engine, "execute_async", None)
243
+ if callable(execute_async):
244
+ async_out = execute_async(ctx)
245
+ if inspect.isawaitable(async_out):
246
+ await async_out
247
+ # 一旦存在 execute_async,就不再回落执行 execute,避免双执行歧义。
248
+ return
249
+ execute = getattr(self._hook_engine, "execute", None)
250
+ if not callable(execute):
251
+ return
252
+ out = execute(ctx)
253
+ if inspect.isawaitable(out):
254
+ await out
255
+
256
+ @staticmethod
257
+ def _extract_accessed_files(result: Any) -> list[str]:
258
+ if not isinstance(result, dict):
259
+ return []
260
+ files = result.get("_accessed_files")
261
+ if not isinstance(files, list):
262
+ return []
263
+ return [item for item in files if isinstance(item, str)]
264
+
265
+ def _build_graph(self) -> CompiledStateGraph:
266
+ if self._graph_factory is None:
267
+ raise RuntimeError("AgentSession requires either graph or graph_factory")
268
+ getter = lambda: list(self._active_files)
269
+ return self._graph_factory(
270
+ active_files_getter=getter,
271
+ container_type=self._container_type,
272
+ enable_trace=self._enable_trace,
273
+ )
274
+
275
+ def _append_transcript_message(self, message: Any) -> None:
276
+ if self._transcript_writer is None:
277
+ return
278
+ record = self._serialize_message_for_transcript(message)
279
+ self._append_transcript_record(record)
280
+
281
+ def _append_compact_boundary(self, *, before_tokens: int, after_tokens: int) -> None:
282
+ record = {
283
+ "type": "compact_boundary",
284
+ "summary": f"compacted context {before_tokens}->{after_tokens}",
285
+ }
286
+ self._append_transcript_record(record)
287
+
288
+ def _append_transcript_record(self, payload: dict[str, Any]) -> None:
289
+ if self._transcript_writer is None:
290
+ return
291
+ uuid = str(uuid4())
292
+ record = {
293
+ **payload,
294
+ "uuid": uuid,
295
+ "logical_parent_uuid": self._last_transcript_uuid,
296
+ "ts": time.time(),
297
+ }
298
+ self._last_transcript_uuid = uuid
299
+ self._transcript_writer.append_message(self._session_id, record)
300
+
301
+ @staticmethod
302
+ def _serialize_message_for_transcript(message: Any) -> dict[str, Any]:
303
+ role = getattr(message, "type", type(message).__name__.lower())
304
+ content = getattr(message, "content", "")
305
+ if isinstance(content, list):
306
+ normalized_content: Any = [str(item) for item in content]
307
+ elif isinstance(content, str):
308
+ normalized_content = content
309
+ else:
310
+ normalized_content = str(content)
311
+ return {"type": str(role), "role": str(role), "content": normalized_content}
312
+
313
+ def _register_builtin_commands(self) -> None:
314
+ self._runtime_command_registry.register(make_clear_command())
315
+ self._runtime_command_registry.register(make_compact_command())
316
+ self._runtime_command_registry.register(make_memory_command())
317
+ self._runtime_command_registry.register(make_reload_plugins_command())
318
+
319
+
320
+ __all__ = ["AgentSession"]
@@ -0,0 +1,87 @@
1
+ """
2
+ session/conversation_factory.py — ConversationSession 入口工厂。
3
+
4
+ 职责:
5
+ 统一组装 ConversationSession 所需 graph_factory、workspace 配置与 hook_engine。
6
+
7
+ 链路位置:
8
+ 作为 Web/IM 对话场景创建 ConversationSession 的便捷入口。
9
+
10
+ 当前裁剪范围:
11
+ 仅覆盖基础参数透传,不包含 HTTP 框架集成。
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from pathlib import Path
17
+ from typing import Any
18
+ from uuid import uuid4
19
+
20
+ from langchain_core.messages import BaseMessage
21
+ from langchain_core.language_models.chat_models import BaseChatModel
22
+
23
+ from ..loop.graph.factory import create_loop_agent
24
+ from ..loop.hook import HookEngine, HooksConfigSnapshot, HookRegistry, WorkspaceTrustChecker
25
+ from ..workspace import resolve_agent_workspace_config
26
+ from .conversation_session import ConversationSession
27
+
28
+
29
+ def create_conversation_session(
30
+ model: str | BaseChatModel,
31
+ *,
32
+ conversation_id: str | None = None,
33
+ workspace_root: str | Path | None = None,
34
+ agent_home: str = ".langchain_agentx",
35
+ hooks: HookRegistry | None = None,
36
+ hook_engine: Any | None = None,
37
+ initial_messages: list[BaseMessage] | None = None,
38
+ permission_resolver: Any | None = None,
39
+ **loop_kwargs: Any,
40
+ ) -> ConversationSession:
41
+ """创建 ConversationSession。conversation_id 未传时自动生成。"""
42
+ resolved_id = conversation_id or f"conv-{uuid4().hex}"
43
+ workspace_cfg = resolve_agent_workspace_config(
44
+ workspace_root=workspace_root or Path.cwd(),
45
+ agent_home=agent_home,
46
+ )
47
+ if hook_engine is None:
48
+ snapshot = hooks.build_snapshot() if hooks is not None else HooksConfigSnapshot()
49
+ hook_engine = HookEngine(
50
+ snapshot,
51
+ trust_checker=WorkspaceTrustChecker(
52
+ headless=False,
53
+ workspace_trust_accepted=workspace_cfg.workspace_trust_accepted,
54
+ ),
55
+ )
56
+
57
+ resolved_permission_resolver = permission_resolver
58
+ if resolved_permission_resolver is None and "permission_resolver" in loop_kwargs:
59
+ resolved_permission_resolver = loop_kwargs.pop("permission_resolver")
60
+
61
+ def _graph_factory(
62
+ session_id: str,
63
+ container_type: str = "conversation",
64
+ **_ignored: Any,
65
+ ) -> Any:
66
+ return create_loop_agent(
67
+ model,
68
+ session_id=session_id,
69
+ workspace_root=workspace_cfg.workspace_root,
70
+ agent_home=workspace_cfg.agent_home,
71
+ workspace_trust_accepted=workspace_cfg.workspace_trust_accepted,
72
+ hooks=hooks,
73
+ permission_resolver=resolved_permission_resolver,
74
+ container_type=container_type,
75
+ **loop_kwargs,
76
+ )
77
+
78
+ return ConversationSession(
79
+ graph_factory=_graph_factory,
80
+ conversation_id=resolved_id,
81
+ workspace_cfg=workspace_cfg,
82
+ hook_engine=hook_engine,
83
+ initial_messages=list(initial_messages or []),
84
+ )
85
+
86
+
87
+ __all__ = ["create_conversation_session"]