agentpool 2.1.9__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.

Potentially problematic release.


This version of agentpool might be problematic. Click here for more details.

Files changed (474) hide show
  1. acp/README.md +64 -0
  2. acp/__init__.py +172 -0
  3. acp/__main__.py +10 -0
  4. acp/acp_requests.py +285 -0
  5. acp/agent/__init__.py +6 -0
  6. acp/agent/connection.py +256 -0
  7. acp/agent/implementations/__init__.py +6 -0
  8. acp/agent/implementations/debug_server/__init__.py +1 -0
  9. acp/agent/implementations/debug_server/cli.py +79 -0
  10. acp/agent/implementations/debug_server/debug.html +234 -0
  11. acp/agent/implementations/debug_server/debug_server.py +496 -0
  12. acp/agent/implementations/testing.py +91 -0
  13. acp/agent/protocol.py +65 -0
  14. acp/bridge/README.md +162 -0
  15. acp/bridge/__init__.py +6 -0
  16. acp/bridge/__main__.py +91 -0
  17. acp/bridge/bridge.py +246 -0
  18. acp/bridge/py.typed +0 -0
  19. acp/bridge/settings.py +15 -0
  20. acp/client/__init__.py +7 -0
  21. acp/client/connection.py +251 -0
  22. acp/client/implementations/__init__.py +7 -0
  23. acp/client/implementations/default_client.py +185 -0
  24. acp/client/implementations/headless_client.py +266 -0
  25. acp/client/implementations/noop_client.py +110 -0
  26. acp/client/protocol.py +61 -0
  27. acp/connection.py +280 -0
  28. acp/exceptions.py +46 -0
  29. acp/filesystem.py +524 -0
  30. acp/notifications.py +832 -0
  31. acp/py.typed +0 -0
  32. acp/schema/__init__.py +265 -0
  33. acp/schema/agent_plan.py +30 -0
  34. acp/schema/agent_requests.py +126 -0
  35. acp/schema/agent_responses.py +256 -0
  36. acp/schema/base.py +39 -0
  37. acp/schema/capabilities.py +230 -0
  38. acp/schema/client_requests.py +247 -0
  39. acp/schema/client_responses.py +96 -0
  40. acp/schema/common.py +81 -0
  41. acp/schema/content_blocks.py +188 -0
  42. acp/schema/mcp.py +82 -0
  43. acp/schema/messages.py +171 -0
  44. acp/schema/notifications.py +82 -0
  45. acp/schema/protocol_stuff.md +3 -0
  46. acp/schema/session_state.py +160 -0
  47. acp/schema/session_updates.py +419 -0
  48. acp/schema/slash_commands.py +51 -0
  49. acp/schema/terminal.py +15 -0
  50. acp/schema/tool_call.py +347 -0
  51. acp/stdio.py +250 -0
  52. acp/task/__init__.py +53 -0
  53. acp/task/debug.py +197 -0
  54. acp/task/dispatcher.py +93 -0
  55. acp/task/queue.py +69 -0
  56. acp/task/sender.py +82 -0
  57. acp/task/state.py +87 -0
  58. acp/task/supervisor.py +93 -0
  59. acp/terminal_handle.py +30 -0
  60. acp/tool_call_reporter.py +199 -0
  61. acp/tool_call_state.py +178 -0
  62. acp/transports.py +104 -0
  63. acp/utils.py +240 -0
  64. agentpool/__init__.py +63 -0
  65. agentpool/__main__.py +7 -0
  66. agentpool/agents/__init__.py +30 -0
  67. agentpool/agents/acp_agent/__init__.py +5 -0
  68. agentpool/agents/acp_agent/acp_agent.py +837 -0
  69. agentpool/agents/acp_agent/acp_converters.py +294 -0
  70. agentpool/agents/acp_agent/client_handler.py +317 -0
  71. agentpool/agents/acp_agent/session_state.py +44 -0
  72. agentpool/agents/agent.py +1264 -0
  73. agentpool/agents/agui_agent/__init__.py +19 -0
  74. agentpool/agents/agui_agent/agui_agent.py +677 -0
  75. agentpool/agents/agui_agent/agui_converters.py +423 -0
  76. agentpool/agents/agui_agent/chunk_transformer.py +204 -0
  77. agentpool/agents/agui_agent/event_types.py +83 -0
  78. agentpool/agents/agui_agent/helpers.py +192 -0
  79. agentpool/agents/architect.py +71 -0
  80. agentpool/agents/base_agent.py +177 -0
  81. agentpool/agents/claude_code_agent/__init__.py +11 -0
  82. agentpool/agents/claude_code_agent/claude_code_agent.py +1021 -0
  83. agentpool/agents/claude_code_agent/converters.py +243 -0
  84. agentpool/agents/context.py +105 -0
  85. agentpool/agents/events/__init__.py +61 -0
  86. agentpool/agents/events/builtin_handlers.py +129 -0
  87. agentpool/agents/events/event_emitter.py +320 -0
  88. agentpool/agents/events/events.py +561 -0
  89. agentpool/agents/events/tts_handlers.py +186 -0
  90. agentpool/agents/interactions.py +419 -0
  91. agentpool/agents/slashed_agent.py +244 -0
  92. agentpool/agents/sys_prompts.py +178 -0
  93. agentpool/agents/tool_wrapping.py +184 -0
  94. agentpool/base_provider.py +28 -0
  95. agentpool/common_types.py +226 -0
  96. agentpool/config_resources/__init__.py +16 -0
  97. agentpool/config_resources/acp_assistant.yml +24 -0
  98. agentpool/config_resources/agents.yml +109 -0
  99. agentpool/config_resources/agents_template.yml +18 -0
  100. agentpool/config_resources/agui_test.yml +18 -0
  101. agentpool/config_resources/claude_code_agent.yml +16 -0
  102. agentpool/config_resources/claude_style_subagent.md +30 -0
  103. agentpool/config_resources/external_acp_agents.yml +77 -0
  104. agentpool/config_resources/opencode_style_subagent.md +19 -0
  105. agentpool/config_resources/tts_test_agents.yml +78 -0
  106. agentpool/delegation/__init__.py +8 -0
  107. agentpool/delegation/base_team.py +504 -0
  108. agentpool/delegation/message_flow_tracker.py +39 -0
  109. agentpool/delegation/pool.py +1129 -0
  110. agentpool/delegation/team.py +325 -0
  111. agentpool/delegation/teamrun.py +343 -0
  112. agentpool/docs/__init__.py +5 -0
  113. agentpool/docs/gen_examples.py +42 -0
  114. agentpool/docs/utils.py +370 -0
  115. agentpool/functional/__init__.py +20 -0
  116. agentpool/functional/py.typed +0 -0
  117. agentpool/functional/run.py +80 -0
  118. agentpool/functional/structure.py +136 -0
  119. agentpool/hooks/__init__.py +20 -0
  120. agentpool/hooks/agent_hooks.py +247 -0
  121. agentpool/hooks/base.py +119 -0
  122. agentpool/hooks/callable.py +140 -0
  123. agentpool/hooks/command.py +180 -0
  124. agentpool/hooks/prompt.py +122 -0
  125. agentpool/jinja_filters.py +132 -0
  126. agentpool/log.py +224 -0
  127. agentpool/mcp_server/__init__.py +17 -0
  128. agentpool/mcp_server/client.py +429 -0
  129. agentpool/mcp_server/constants.py +32 -0
  130. agentpool/mcp_server/conversions.py +172 -0
  131. agentpool/mcp_server/helpers.py +47 -0
  132. agentpool/mcp_server/manager.py +232 -0
  133. agentpool/mcp_server/message_handler.py +164 -0
  134. agentpool/mcp_server/registries/__init__.py +1 -0
  135. agentpool/mcp_server/registries/official_registry_client.py +345 -0
  136. agentpool/mcp_server/registries/pulsemcp_client.py +88 -0
  137. agentpool/mcp_server/tool_bridge.py +548 -0
  138. agentpool/messaging/__init__.py +58 -0
  139. agentpool/messaging/compaction.py +928 -0
  140. agentpool/messaging/connection_manager.py +319 -0
  141. agentpool/messaging/context.py +66 -0
  142. agentpool/messaging/event_manager.py +426 -0
  143. agentpool/messaging/events.py +39 -0
  144. agentpool/messaging/message_container.py +209 -0
  145. agentpool/messaging/message_history.py +491 -0
  146. agentpool/messaging/messagenode.py +377 -0
  147. agentpool/messaging/messages.py +655 -0
  148. agentpool/messaging/processing.py +76 -0
  149. agentpool/mime_utils.py +95 -0
  150. agentpool/models/__init__.py +21 -0
  151. agentpool/models/acp_agents/__init__.py +22 -0
  152. agentpool/models/acp_agents/base.py +308 -0
  153. agentpool/models/acp_agents/mcp_capable.py +790 -0
  154. agentpool/models/acp_agents/non_mcp.py +842 -0
  155. agentpool/models/agents.py +450 -0
  156. agentpool/models/agui_agents.py +89 -0
  157. agentpool/models/claude_code_agents.py +238 -0
  158. agentpool/models/file_agents.py +116 -0
  159. agentpool/models/file_parsing.py +367 -0
  160. agentpool/models/manifest.py +658 -0
  161. agentpool/observability/__init__.py +9 -0
  162. agentpool/observability/observability_registry.py +97 -0
  163. agentpool/prompts/__init__.py +1 -0
  164. agentpool/prompts/base.py +27 -0
  165. agentpool/prompts/builtin_provider.py +75 -0
  166. agentpool/prompts/conversion_manager.py +95 -0
  167. agentpool/prompts/convert.py +96 -0
  168. agentpool/prompts/manager.py +204 -0
  169. agentpool/prompts/parts/zed.md +33 -0
  170. agentpool/prompts/prompts.py +581 -0
  171. agentpool/py.typed +0 -0
  172. agentpool/queries/tree-sitter-language-pack/README.md +7 -0
  173. agentpool/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
  174. agentpool/queries/tree-sitter-language-pack/c-tags.scm +9 -0
  175. agentpool/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
  176. agentpool/queries/tree-sitter-language-pack/clojure-tags.scm +7 -0
  177. agentpool/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
  178. agentpool/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
  179. agentpool/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
  180. agentpool/queries/tree-sitter-language-pack/d-tags.scm +26 -0
  181. agentpool/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
  182. agentpool/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
  183. agentpool/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
  184. agentpool/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
  185. agentpool/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
  186. agentpool/queries/tree-sitter-language-pack/go-tags.scm +42 -0
  187. agentpool/queries/tree-sitter-language-pack/java-tags.scm +20 -0
  188. agentpool/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
  189. agentpool/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
  190. agentpool/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
  191. agentpool/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
  192. agentpool/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
  193. agentpool/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
  194. agentpool/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
  195. agentpool/queries/tree-sitter-language-pack/python-tags.scm +14 -0
  196. agentpool/queries/tree-sitter-language-pack/r-tags.scm +21 -0
  197. agentpool/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
  198. agentpool/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
  199. agentpool/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
  200. agentpool/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
  201. agentpool/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
  202. agentpool/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
  203. agentpool/queries/tree-sitter-languages/README.md +24 -0
  204. agentpool/queries/tree-sitter-languages/c-tags.scm +9 -0
  205. agentpool/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
  206. agentpool/queries/tree-sitter-languages/cpp-tags.scm +15 -0
  207. agentpool/queries/tree-sitter-languages/dart-tags.scm +91 -0
  208. agentpool/queries/tree-sitter-languages/elisp-tags.scm +8 -0
  209. agentpool/queries/tree-sitter-languages/elixir-tags.scm +54 -0
  210. agentpool/queries/tree-sitter-languages/elm-tags.scm +19 -0
  211. agentpool/queries/tree-sitter-languages/fortran-tags.scm +15 -0
  212. agentpool/queries/tree-sitter-languages/go-tags.scm +30 -0
  213. agentpool/queries/tree-sitter-languages/haskell-tags.scm +3 -0
  214. agentpool/queries/tree-sitter-languages/hcl-tags.scm +77 -0
  215. agentpool/queries/tree-sitter-languages/java-tags.scm +20 -0
  216. agentpool/queries/tree-sitter-languages/javascript-tags.scm +88 -0
  217. agentpool/queries/tree-sitter-languages/julia-tags.scm +60 -0
  218. agentpool/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
  219. agentpool/queries/tree-sitter-languages/matlab-tags.scm +10 -0
  220. agentpool/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
  221. agentpool/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
  222. agentpool/queries/tree-sitter-languages/php-tags.scm +26 -0
  223. agentpool/queries/tree-sitter-languages/python-tags.scm +12 -0
  224. agentpool/queries/tree-sitter-languages/ql-tags.scm +26 -0
  225. agentpool/queries/tree-sitter-languages/ruby-tags.scm +64 -0
  226. agentpool/queries/tree-sitter-languages/rust-tags.scm +60 -0
  227. agentpool/queries/tree-sitter-languages/scala-tags.scm +65 -0
  228. agentpool/queries/tree-sitter-languages/typescript-tags.scm +41 -0
  229. agentpool/queries/tree-sitter-languages/zig-tags.scm +3 -0
  230. agentpool/repomap.py +1231 -0
  231. agentpool/resource_providers/__init__.py +17 -0
  232. agentpool/resource_providers/aggregating.py +54 -0
  233. agentpool/resource_providers/base.py +172 -0
  234. agentpool/resource_providers/codemode/__init__.py +9 -0
  235. agentpool/resource_providers/codemode/code_executor.py +215 -0
  236. agentpool/resource_providers/codemode/default_prompt.py +19 -0
  237. agentpool/resource_providers/codemode/helpers.py +83 -0
  238. agentpool/resource_providers/codemode/progress_executor.py +212 -0
  239. agentpool/resource_providers/codemode/provider.py +150 -0
  240. agentpool/resource_providers/codemode/remote_mcp_execution.py +143 -0
  241. agentpool/resource_providers/codemode/remote_provider.py +171 -0
  242. agentpool/resource_providers/filtering.py +42 -0
  243. agentpool/resource_providers/mcp_provider.py +246 -0
  244. agentpool/resource_providers/plan_provider.py +196 -0
  245. agentpool/resource_providers/pool.py +69 -0
  246. agentpool/resource_providers/static.py +289 -0
  247. agentpool/running/__init__.py +20 -0
  248. agentpool/running/decorators.py +56 -0
  249. agentpool/running/discovery.py +101 -0
  250. agentpool/running/executor.py +284 -0
  251. agentpool/running/injection.py +111 -0
  252. agentpool/running/py.typed +0 -0
  253. agentpool/running/run_nodes.py +87 -0
  254. agentpool/server.py +122 -0
  255. agentpool/sessions/__init__.py +13 -0
  256. agentpool/sessions/manager.py +302 -0
  257. agentpool/sessions/models.py +71 -0
  258. agentpool/sessions/session.py +239 -0
  259. agentpool/sessions/store.py +163 -0
  260. agentpool/skills/__init__.py +5 -0
  261. agentpool/skills/manager.py +120 -0
  262. agentpool/skills/registry.py +210 -0
  263. agentpool/skills/skill.py +36 -0
  264. agentpool/storage/__init__.py +17 -0
  265. agentpool/storage/manager.py +419 -0
  266. agentpool/storage/serialization.py +136 -0
  267. agentpool/talk/__init__.py +13 -0
  268. agentpool/talk/registry.py +128 -0
  269. agentpool/talk/stats.py +159 -0
  270. agentpool/talk/talk.py +604 -0
  271. agentpool/tasks/__init__.py +20 -0
  272. agentpool/tasks/exceptions.py +25 -0
  273. agentpool/tasks/registry.py +33 -0
  274. agentpool/testing.py +129 -0
  275. agentpool/text_templates/__init__.py +39 -0
  276. agentpool/text_templates/system_prompt.jinja +30 -0
  277. agentpool/text_templates/tool_call_default.jinja +13 -0
  278. agentpool/text_templates/tool_call_markdown.jinja +25 -0
  279. agentpool/text_templates/tool_call_simple.jinja +5 -0
  280. agentpool/tools/__init__.py +16 -0
  281. agentpool/tools/base.py +269 -0
  282. agentpool/tools/exceptions.py +9 -0
  283. agentpool/tools/manager.py +255 -0
  284. agentpool/tools/tool_call_info.py +87 -0
  285. agentpool/ui/__init__.py +2 -0
  286. agentpool/ui/base.py +89 -0
  287. agentpool/ui/mock_provider.py +81 -0
  288. agentpool/ui/stdlib_provider.py +150 -0
  289. agentpool/utils/__init__.py +44 -0
  290. agentpool/utils/baseregistry.py +185 -0
  291. agentpool/utils/count_tokens.py +62 -0
  292. agentpool/utils/dag.py +184 -0
  293. agentpool/utils/importing.py +206 -0
  294. agentpool/utils/inspection.py +334 -0
  295. agentpool/utils/model_capabilities.py +25 -0
  296. agentpool/utils/network.py +28 -0
  297. agentpool/utils/now.py +22 -0
  298. agentpool/utils/parse_time.py +87 -0
  299. agentpool/utils/result_utils.py +35 -0
  300. agentpool/utils/signatures.py +305 -0
  301. agentpool/utils/streams.py +112 -0
  302. agentpool/utils/tasks.py +186 -0
  303. agentpool/vfs_registry.py +250 -0
  304. agentpool-2.1.9.dist-info/METADATA +336 -0
  305. agentpool-2.1.9.dist-info/RECORD +474 -0
  306. agentpool-2.1.9.dist-info/WHEEL +4 -0
  307. agentpool-2.1.9.dist-info/entry_points.txt +14 -0
  308. agentpool-2.1.9.dist-info/licenses/LICENSE +22 -0
  309. agentpool_cli/__init__.py +34 -0
  310. agentpool_cli/__main__.py +66 -0
  311. agentpool_cli/agent.py +175 -0
  312. agentpool_cli/cli_types.py +23 -0
  313. agentpool_cli/common.py +163 -0
  314. agentpool_cli/create.py +175 -0
  315. agentpool_cli/history.py +217 -0
  316. agentpool_cli/log.py +78 -0
  317. agentpool_cli/py.typed +0 -0
  318. agentpool_cli/run.py +84 -0
  319. agentpool_cli/serve_acp.py +177 -0
  320. agentpool_cli/serve_api.py +69 -0
  321. agentpool_cli/serve_mcp.py +74 -0
  322. agentpool_cli/serve_vercel.py +233 -0
  323. agentpool_cli/store.py +171 -0
  324. agentpool_cli/task.py +84 -0
  325. agentpool_cli/utils.py +104 -0
  326. agentpool_cli/watch.py +54 -0
  327. agentpool_commands/__init__.py +180 -0
  328. agentpool_commands/agents.py +199 -0
  329. agentpool_commands/base.py +45 -0
  330. agentpool_commands/commands.py +58 -0
  331. agentpool_commands/completers.py +110 -0
  332. agentpool_commands/connections.py +175 -0
  333. agentpool_commands/markdown_utils.py +31 -0
  334. agentpool_commands/models.py +62 -0
  335. agentpool_commands/prompts.py +78 -0
  336. agentpool_commands/py.typed +0 -0
  337. agentpool_commands/read.py +77 -0
  338. agentpool_commands/resources.py +210 -0
  339. agentpool_commands/session.py +48 -0
  340. agentpool_commands/tools.py +269 -0
  341. agentpool_commands/utils.py +189 -0
  342. agentpool_commands/workers.py +163 -0
  343. agentpool_config/__init__.py +53 -0
  344. agentpool_config/builtin_tools.py +265 -0
  345. agentpool_config/commands.py +237 -0
  346. agentpool_config/conditions.py +301 -0
  347. agentpool_config/converters.py +30 -0
  348. agentpool_config/durable.py +331 -0
  349. agentpool_config/event_handlers.py +600 -0
  350. agentpool_config/events.py +153 -0
  351. agentpool_config/forward_targets.py +251 -0
  352. agentpool_config/hook_conditions.py +331 -0
  353. agentpool_config/hooks.py +241 -0
  354. agentpool_config/jinja.py +206 -0
  355. agentpool_config/knowledge.py +41 -0
  356. agentpool_config/loaders.py +350 -0
  357. agentpool_config/mcp_server.py +243 -0
  358. agentpool_config/nodes.py +202 -0
  359. agentpool_config/observability.py +191 -0
  360. agentpool_config/output_types.py +55 -0
  361. agentpool_config/pool_server.py +267 -0
  362. agentpool_config/prompt_hubs.py +105 -0
  363. agentpool_config/prompts.py +185 -0
  364. agentpool_config/py.typed +0 -0
  365. agentpool_config/resources.py +33 -0
  366. agentpool_config/session.py +119 -0
  367. agentpool_config/skills.py +17 -0
  368. agentpool_config/storage.py +288 -0
  369. agentpool_config/system_prompts.py +190 -0
  370. agentpool_config/task.py +162 -0
  371. agentpool_config/teams.py +52 -0
  372. agentpool_config/tools.py +112 -0
  373. agentpool_config/toolsets.py +1033 -0
  374. agentpool_config/workers.py +86 -0
  375. agentpool_prompts/__init__.py +1 -0
  376. agentpool_prompts/braintrust_hub.py +235 -0
  377. agentpool_prompts/fabric.py +75 -0
  378. agentpool_prompts/langfuse_hub.py +79 -0
  379. agentpool_prompts/promptlayer_provider.py +59 -0
  380. agentpool_prompts/py.typed +0 -0
  381. agentpool_server/__init__.py +9 -0
  382. agentpool_server/a2a_server/__init__.py +5 -0
  383. agentpool_server/a2a_server/a2a_types.py +41 -0
  384. agentpool_server/a2a_server/server.py +190 -0
  385. agentpool_server/a2a_server/storage.py +81 -0
  386. agentpool_server/acp_server/__init__.py +22 -0
  387. agentpool_server/acp_server/acp_agent.py +786 -0
  388. agentpool_server/acp_server/acp_tools.py +43 -0
  389. agentpool_server/acp_server/commands/__init__.py +18 -0
  390. agentpool_server/acp_server/commands/acp_commands.py +594 -0
  391. agentpool_server/acp_server/commands/debug_commands.py +376 -0
  392. agentpool_server/acp_server/commands/docs_commands/__init__.py +39 -0
  393. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +169 -0
  394. agentpool_server/acp_server/commands/docs_commands/get_schema.py +176 -0
  395. agentpool_server/acp_server/commands/docs_commands/get_source.py +110 -0
  396. agentpool_server/acp_server/commands/docs_commands/git_diff.py +111 -0
  397. agentpool_server/acp_server/commands/docs_commands/helpers.py +33 -0
  398. agentpool_server/acp_server/commands/docs_commands/url_to_markdown.py +90 -0
  399. agentpool_server/acp_server/commands/spawn.py +210 -0
  400. agentpool_server/acp_server/converters.py +235 -0
  401. agentpool_server/acp_server/input_provider.py +338 -0
  402. agentpool_server/acp_server/server.py +288 -0
  403. agentpool_server/acp_server/session.py +969 -0
  404. agentpool_server/acp_server/session_manager.py +313 -0
  405. agentpool_server/acp_server/syntax_detection.py +250 -0
  406. agentpool_server/acp_server/zed_tools.md +90 -0
  407. agentpool_server/aggregating_server.py +309 -0
  408. agentpool_server/agui_server/__init__.py +11 -0
  409. agentpool_server/agui_server/server.py +128 -0
  410. agentpool_server/base.py +189 -0
  411. agentpool_server/http_server.py +164 -0
  412. agentpool_server/mcp_server/__init__.py +6 -0
  413. agentpool_server/mcp_server/server.py +314 -0
  414. agentpool_server/mcp_server/zed_wrapper.py +110 -0
  415. agentpool_server/openai_api_server/__init__.py +5 -0
  416. agentpool_server/openai_api_server/completions/__init__.py +1 -0
  417. agentpool_server/openai_api_server/completions/helpers.py +81 -0
  418. agentpool_server/openai_api_server/completions/models.py +98 -0
  419. agentpool_server/openai_api_server/responses/__init__.py +1 -0
  420. agentpool_server/openai_api_server/responses/helpers.py +74 -0
  421. agentpool_server/openai_api_server/responses/models.py +96 -0
  422. agentpool_server/openai_api_server/server.py +242 -0
  423. agentpool_server/py.typed +0 -0
  424. agentpool_storage/__init__.py +9 -0
  425. agentpool_storage/base.py +310 -0
  426. agentpool_storage/file_provider.py +378 -0
  427. agentpool_storage/formatters.py +129 -0
  428. agentpool_storage/memory_provider.py +396 -0
  429. agentpool_storage/models.py +108 -0
  430. agentpool_storage/py.typed +0 -0
  431. agentpool_storage/session_store.py +262 -0
  432. agentpool_storage/sql_provider/__init__.py +21 -0
  433. agentpool_storage/sql_provider/cli.py +146 -0
  434. agentpool_storage/sql_provider/models.py +249 -0
  435. agentpool_storage/sql_provider/queries.py +15 -0
  436. agentpool_storage/sql_provider/sql_provider.py +444 -0
  437. agentpool_storage/sql_provider/utils.py +234 -0
  438. agentpool_storage/text_log_provider.py +275 -0
  439. agentpool_toolsets/__init__.py +15 -0
  440. agentpool_toolsets/builtin/__init__.py +33 -0
  441. agentpool_toolsets/builtin/agent_management.py +239 -0
  442. agentpool_toolsets/builtin/chain.py +288 -0
  443. agentpool_toolsets/builtin/code.py +398 -0
  444. agentpool_toolsets/builtin/debug.py +291 -0
  445. agentpool_toolsets/builtin/execution_environment.py +381 -0
  446. agentpool_toolsets/builtin/file_edit/__init__.py +11 -0
  447. agentpool_toolsets/builtin/file_edit/file_edit.py +747 -0
  448. agentpool_toolsets/builtin/file_edit/fuzzy_matcher/__init__.py +5 -0
  449. agentpool_toolsets/builtin/file_edit/fuzzy_matcher/example_usage.py +311 -0
  450. agentpool_toolsets/builtin/file_edit/fuzzy_matcher/streaming_fuzzy_matcher.py +443 -0
  451. agentpool_toolsets/builtin/history.py +36 -0
  452. agentpool_toolsets/builtin/integration.py +85 -0
  453. agentpool_toolsets/builtin/skills.py +77 -0
  454. agentpool_toolsets/builtin/subagent_tools.py +324 -0
  455. agentpool_toolsets/builtin/tool_management.py +90 -0
  456. agentpool_toolsets/builtin/user_interaction.py +52 -0
  457. agentpool_toolsets/builtin/workers.py +128 -0
  458. agentpool_toolsets/composio_toolset.py +96 -0
  459. agentpool_toolsets/config_creation.py +192 -0
  460. agentpool_toolsets/entry_points.py +47 -0
  461. agentpool_toolsets/fsspec_toolset/__init__.py +7 -0
  462. agentpool_toolsets/fsspec_toolset/diagnostics.py +115 -0
  463. agentpool_toolsets/fsspec_toolset/grep.py +450 -0
  464. agentpool_toolsets/fsspec_toolset/helpers.py +631 -0
  465. agentpool_toolsets/fsspec_toolset/streaming_diff_parser.py +249 -0
  466. agentpool_toolsets/fsspec_toolset/toolset.py +1384 -0
  467. agentpool_toolsets/mcp_run_toolset.py +61 -0
  468. agentpool_toolsets/notifications.py +146 -0
  469. agentpool_toolsets/openapi.py +118 -0
  470. agentpool_toolsets/py.typed +0 -0
  471. agentpool_toolsets/search_toolset.py +202 -0
  472. agentpool_toolsets/semantic_memory_toolset.py +536 -0
  473. agentpool_toolsets/streaming_tools.py +265 -0
  474. agentpool_toolsets/vfs_toolset.py +124 -0
@@ -0,0 +1,247 @@
1
+ """AgentHooks - Runtime hook container for agent lifecycle events."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from dataclasses import dataclass, field
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ from agentpool.hooks.base import HookResult
10
+ from agentpool.log import get_logger
11
+
12
+
13
+ if TYPE_CHECKING:
14
+ from agentpool.hooks.base import Hook, HookInput
15
+
16
+
17
+ logger = get_logger(__name__)
18
+
19
+
20
+ @dataclass
21
+ class AgentHooks:
22
+ """Runtime container for agent lifecycle hooks.
23
+
24
+ Holds instantiated hooks organized by event type and provides
25
+ methods to execute them with proper input/output handling.
26
+
27
+ Attributes:
28
+ pre_run: Hooks executed before agent.run() processes a prompt.
29
+ post_run: Hooks executed after agent.run() completes.
30
+ pre_tool_use: Hooks executed before a tool is called.
31
+ post_tool_use: Hooks executed after a tool completes.
32
+ """
33
+
34
+ pre_run: list[Hook] = field(default_factory=list)
35
+ post_run: list[Hook] = field(default_factory=list)
36
+ pre_tool_use: list[Hook] = field(default_factory=list)
37
+ post_tool_use: list[Hook] = field(default_factory=list)
38
+
39
+ def has_hooks(self) -> bool:
40
+ """Check if any hooks are configured."""
41
+ return bool(self.pre_run or self.post_run or self.pre_tool_use or self.post_tool_use)
42
+
43
+ async def run_pre_run_hooks(
44
+ self,
45
+ *,
46
+ agent_name: str,
47
+ prompt: str,
48
+ conversation_id: str | None = None,
49
+ ) -> HookResult:
50
+ """Execute pre-run hooks.
51
+
52
+ Args:
53
+ agent_name: Name of the agent.
54
+ prompt: The prompt being processed.
55
+ conversation_id: Optional conversation identifier.
56
+
57
+ Returns:
58
+ Combined hook result. If any hook denies, the run should be blocked.
59
+ """
60
+ input_data: HookInput = {
61
+ "event": "pre_run",
62
+ "agent_name": agent_name,
63
+ "prompt": prompt,
64
+ "conversation_id": conversation_id,
65
+ }
66
+ return await self._run_hooks(self.pre_run, input_data)
67
+
68
+ async def run_post_run_hooks(
69
+ self,
70
+ *,
71
+ agent_name: str,
72
+ prompt: str,
73
+ result: Any,
74
+ conversation_id: str | None = None,
75
+ ) -> HookResult:
76
+ """Execute post-run hooks.
77
+
78
+ Args:
79
+ agent_name: Name of the agent.
80
+ prompt: The prompt that was processed.
81
+ result: The result from the run.
82
+ conversation_id: Optional conversation identifier.
83
+
84
+ Returns:
85
+ Combined hook result.
86
+ """
87
+ input_data: HookInput = {
88
+ "event": "post_run",
89
+ "agent_name": agent_name,
90
+ "prompt": prompt,
91
+ "result": result,
92
+ "conversation_id": conversation_id,
93
+ }
94
+ return await self._run_hooks(self.post_run, input_data)
95
+
96
+ async def run_pre_tool_hooks(
97
+ self,
98
+ *,
99
+ agent_name: str,
100
+ tool_name: str,
101
+ tool_input: dict[str, Any],
102
+ conversation_id: str | None = None,
103
+ ) -> HookResult:
104
+ """Execute pre-tool-use hooks.
105
+
106
+ Args:
107
+ agent_name: Name of the agent.
108
+ tool_name: Name of the tool being called.
109
+ tool_input: Input arguments for the tool.
110
+ conversation_id: Optional conversation identifier.
111
+
112
+ Returns:
113
+ Combined hook result. If any hook denies, the tool call should be blocked.
114
+ May include modified_input to change tool arguments.
115
+ """
116
+ input_data: HookInput = {
117
+ "event": "pre_tool_use",
118
+ "agent_name": agent_name,
119
+ "tool_name": tool_name,
120
+ "tool_input": tool_input,
121
+ "conversation_id": conversation_id,
122
+ }
123
+ return await self._run_hooks(self.pre_tool_use, input_data)
124
+
125
+ async def run_post_tool_hooks(
126
+ self,
127
+ *,
128
+ agent_name: str,
129
+ tool_name: str,
130
+ tool_input: dict[str, Any],
131
+ tool_output: Any,
132
+ duration_ms: float,
133
+ conversation_id: str | None = None,
134
+ ) -> HookResult:
135
+ """Execute post-tool-use hooks.
136
+
137
+ Args:
138
+ agent_name: Name of the agent.
139
+ tool_name: Name of the tool that was called.
140
+ tool_input: Input arguments that were passed to the tool.
141
+ tool_output: Output from the tool.
142
+ duration_ms: How long the tool took to execute.
143
+ conversation_id: Optional conversation identifier.
144
+
145
+ Returns:
146
+ Combined hook result. May include additional_context to inject.
147
+ """
148
+ input_data: HookInput = {
149
+ "event": "post_tool_use",
150
+ "agent_name": agent_name,
151
+ "tool_name": tool_name,
152
+ "tool_input": tool_input,
153
+ "tool_output": tool_output,
154
+ "duration_ms": duration_ms,
155
+ "conversation_id": conversation_id,
156
+ }
157
+ return await self._run_hooks(self.post_tool_use, input_data)
158
+
159
+ @staticmethod
160
+ async def _run_hooks(hooks: list[Hook], input_data: HookInput) -> HookResult:
161
+ """Run a list of hooks and combine their results.
162
+
163
+ Hooks are run in parallel. Results are combined:
164
+ - If any hook returns "deny", the combined result is "deny"
165
+ - If any hook returns "ask", the combined result is "ask" (unless denied)
166
+ - Reasons are concatenated
167
+ - modified_input values are merged (later hooks override earlier)
168
+ - additional_context values are concatenated
169
+ - continue_ is False if any hook sets it False
170
+
171
+ Args:
172
+ hooks: List of hooks to execute.
173
+ input_data: Input data for the hooks.
174
+
175
+ Returns:
176
+ Combined hook result.
177
+ """
178
+ if not hooks:
179
+ return HookResult(decision="allow")
180
+
181
+ # Filter to matching hooks
182
+ matching = [h for h in hooks if h.matches(input_data)]
183
+ if not matching:
184
+ return HookResult(decision="allow")
185
+
186
+ # Run all matching hooks in parallel
187
+ raw_results = await asyncio.gather(
188
+ *(hook.execute(input_data) for hook in matching),
189
+ return_exceptions=True,
190
+ )
191
+
192
+ # Combine results
193
+ combined: HookResult = {"decision": "allow"}
194
+ reasons: list[str] = []
195
+ contexts: list[str] = []
196
+
197
+ for raw_result in raw_results:
198
+ if isinstance(raw_result, BaseException):
199
+ logger.warning("Hook execution failed", error=str(raw_result))
200
+ continue
201
+
202
+ result: HookResult = raw_result
203
+
204
+ # Decision priority: deny > ask > allow
205
+ if result.get("decision") == "deny":
206
+ combined["decision"] = "deny"
207
+ elif result.get("decision") == "ask" and combined.get("decision") != "deny":
208
+ combined["decision"] = "ask"
209
+
210
+ # Collect reasons
211
+ if reason := result.get("reason"):
212
+ reasons.append(reason)
213
+
214
+ # Merge modified_input (later overrides earlier)
215
+ if modified := result.get("modified_input"):
216
+ if "modified_input" not in combined:
217
+ combined["modified_input"] = {}
218
+ combined["modified_input"].update(modified)
219
+
220
+ # Collect additional context
221
+ if ctx := result.get("additional_context"):
222
+ contexts.append(ctx)
223
+
224
+ # continue_ is False if any hook sets it False
225
+ if result.get("continue_") is False:
226
+ combined["continue_"] = False
227
+
228
+ # Combine collected values
229
+ if reasons:
230
+ combined["reason"] = "; ".join(reasons)
231
+ if contexts:
232
+ combined["additional_context"] = "\n".join(contexts)
233
+
234
+ return combined
235
+
236
+ def __repr__(self) -> str:
237
+ counts = {
238
+ "pre_run": len(self.pre_run),
239
+ "post_run": len(self.post_run),
240
+ "pre_tool_use": len(self.pre_tool_use),
241
+ "post_tool_use": len(self.post_tool_use),
242
+ }
243
+ non_empty = {k: v for k, v in counts.items() if v > 0}
244
+ if not non_empty:
245
+ return "AgentHooks(empty)"
246
+ parts = ", ".join(f"{k}={v}" for k, v in non_empty.items())
247
+ return f"AgentHooks({parts})"
@@ -0,0 +1,119 @@
1
+ """Base hook classes and types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ import re
7
+ from typing import Any, Literal, TypedDict
8
+
9
+ from agentpool.log import get_logger
10
+
11
+
12
+ logger = get_logger(__name__)
13
+
14
+ HookEvent = Literal["pre_run", "post_run", "pre_tool_use", "post_tool_use"]
15
+
16
+
17
+ class HookInput(TypedDict, total=False):
18
+ """Input data passed to hooks."""
19
+
20
+ # Common fields
21
+ event: HookEvent
22
+ agent_name: str
23
+ conversation_id: str | None
24
+
25
+ # Tool-related fields (pre_tool_use, post_tool_use)
26
+ tool_name: str
27
+ tool_input: dict[str, Any]
28
+ tool_output: Any
29
+ duration_ms: float
30
+
31
+ # Run-related fields (pre_run, post_run)
32
+ prompt: str
33
+ result: Any
34
+
35
+
36
+ class HookResult(TypedDict, total=False):
37
+ """Result returned from hook execution."""
38
+
39
+ decision: Literal["allow", "deny", "ask"]
40
+ """Decision for pre_* hooks: allow, deny, or ask user."""
41
+
42
+ reason: str
43
+ """Explanation for the decision."""
44
+
45
+ modified_input: dict[str, Any]
46
+ """Modified input for pre_* hooks (e.g., modified tool_input)."""
47
+
48
+ additional_context: str
49
+ """Context to inject into conversation."""
50
+
51
+ continue_: bool
52
+ """Whether to continue execution. False = stop."""
53
+
54
+
55
+ class Hook(ABC):
56
+ """Base class for runtime hooks."""
57
+
58
+ def __init__(
59
+ self,
60
+ event: HookEvent,
61
+ matcher: str | None = None,
62
+ timeout: float = 60.0,
63
+ enabled: bool = True,
64
+ ) -> None:
65
+ """Initialize hook.
66
+
67
+ Args:
68
+ event: The lifecycle event this hook handles.
69
+ matcher: Regex pattern for matching (e.g., tool names). None matches all.
70
+ timeout: Maximum execution time in seconds.
71
+ enabled: Whether this hook is active.
72
+ """
73
+ self.event = event
74
+ self.matcher = matcher
75
+ self.timeout = timeout
76
+ self.enabled = enabled
77
+ self._pattern = re.compile(matcher) if matcher and matcher != "*" else None
78
+
79
+ def matches(self, input_data: HookInput) -> bool:
80
+ """Check if this hook should run for the given input.
81
+
82
+ Args:
83
+ input_data: The hook input data.
84
+
85
+ Returns:
86
+ True if the hook should execute.
87
+ """
88
+ if not self.enabled:
89
+ return False
90
+
91
+ # No pattern means match all
92
+ if self._pattern is None:
93
+ return True
94
+
95
+ # For tool events, match against tool_name
96
+ if self.event in ("pre_tool_use", "post_tool_use"):
97
+ tool_name = input_data.get("tool_name", "")
98
+ return bool(self._pattern.search(tool_name))
99
+
100
+ # For run events, pattern matching doesn't apply (no tool name to match)
101
+ return True
102
+
103
+ @abstractmethod
104
+ async def execute(self, input_data: HookInput) -> HookResult:
105
+ """Execute the hook.
106
+
107
+ Args:
108
+ input_data: The hook input data.
109
+
110
+ Returns:
111
+ Hook result with decision and optional modifications.
112
+ """
113
+ ...
114
+
115
+ def __repr__(self) -> str:
116
+ return (
117
+ f"{self.__class__.__name__}("
118
+ f"event={self.event!r}, matcher={self.matcher!r}, enabled={self.enabled})"
119
+ )
@@ -0,0 +1,140 @@
1
+ """Callable hook implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from agentpool.hooks.base import Hook, HookResult
9
+ from agentpool.log import get_logger
10
+ from agentpool.utils.importing import import_callable
11
+
12
+
13
+ if TYPE_CHECKING:
14
+ from collections.abc import Callable
15
+
16
+ from agentpool.hooks.base import HookEvent, HookInput
17
+
18
+
19
+ logger = get_logger(__name__)
20
+
21
+
22
+ class CallableHook(Hook):
23
+ """Hook that executes a Python callable.
24
+
25
+ The callable receives hook input as a dictionary and should return
26
+ a HookResult dictionary or None.
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ event: HookEvent,
32
+ fn: Callable[..., HookResult | None] | str,
33
+ matcher: str | None = None,
34
+ timeout: float = 60.0,
35
+ enabled: bool = True,
36
+ arguments: dict[str, Any] | None = None,
37
+ ):
38
+ """Initialize callable hook.
39
+
40
+ Args:
41
+ event: The lifecycle event this hook handles.
42
+ fn: The callable to execute, or import path string.
43
+ matcher: Regex pattern for matching.
44
+ timeout: Maximum execution time in seconds.
45
+ enabled: Whether this hook is active.
46
+ arguments: Additional keyword arguments for the callable.
47
+ """
48
+ super().__init__(event=event, matcher=matcher, timeout=timeout, enabled=enabled)
49
+ self._callable: Callable[..., HookResult | None] | None = None
50
+ self._import_path: str | None = None
51
+
52
+ if isinstance(fn, str):
53
+ self._import_path = fn
54
+ else:
55
+ self._callable = fn
56
+
57
+ self.arguments = arguments or {}
58
+
59
+ @property
60
+ def callable(self) -> Callable[..., HookResult | None]:
61
+ """Get the callable, importing lazily if needed."""
62
+ if self._callable is None:
63
+ if self._import_path is None:
64
+ msg = "No callable or import path provided"
65
+ raise ValueError(msg)
66
+ self._callable = import_callable(self._import_path)
67
+ return self._callable
68
+
69
+ async def execute(self, input_data: HookInput) -> HookResult:
70
+ """Execute the callable.
71
+
72
+ Args:
73
+ input_data: The hook input data.
74
+
75
+ Returns:
76
+ Hook result from callable.
77
+ """
78
+ try:
79
+ fn = self.callable
80
+ # Merge input data with additional arguments
81
+ kwargs = {**dict(input_data), **self.arguments}
82
+ # Execute with timeout
83
+ if asyncio.iscoroutinefunction(fn):
84
+ result = await asyncio.wait_for(fn(**kwargs), timeout=self.timeout) # ty: ignore
85
+ else:
86
+ # Run sync function in executor
87
+ loop = asyncio.get_event_loop()
88
+ result = await asyncio.wait_for(
89
+ loop.run_in_executor(None, lambda: fn(**kwargs)),
90
+ timeout=self.timeout,
91
+ )
92
+
93
+ # Normalize result
94
+ if result is None:
95
+ return HookResult(decision="allow")
96
+
97
+ return _normalize_result(result)
98
+
99
+ except TimeoutError:
100
+ fn_path = self._import_path or str(self._callable)
101
+ logger.exception("Hook callable timed out", timeout=self.timeout, callable=fn_path)
102
+ return HookResult(decision="allow")
103
+ except Exception as e:
104
+ fn_path = self._import_path or str(self._callable)
105
+ logger.exception("Hook callable failed", callable=fn_path)
106
+ return HookResult(decision="allow", reason=str(e))
107
+
108
+
109
+ def _normalize_result(result: Any) -> HookResult:
110
+ """Normalize callable result to HookResult.
111
+
112
+ Args:
113
+ result: Result from callable.
114
+
115
+ Returns:
116
+ Normalized hook result.
117
+ """
118
+ if isinstance(result, dict):
119
+ # Already a dict, ensure proper typing
120
+ normalized: HookResult = {}
121
+ if "decision" in result:
122
+ normalized["decision"] = result["decision"]
123
+ if "reason" in result:
124
+ normalized["reason"] = result["reason"]
125
+ if "modified_input" in result:
126
+ normalized["modified_input"] = result["modified_input"]
127
+ if "additional_context" in result:
128
+ normalized["additional_context"] = result["additional_context"]
129
+ if "continue_" in result:
130
+ normalized["continue_"] = result["continue_"]
131
+ return normalized
132
+
133
+ # String result treated as additional context
134
+ if isinstance(result, str):
135
+ return HookResult(decision="allow", additional_context=result)
136
+ # Bool result treated as allow/deny
137
+ if isinstance(result, bool):
138
+ return HookResult(decision="allow" if result else "deny")
139
+ # Unknown type, allow by default
140
+ return HookResult(decision="allow")
@@ -0,0 +1,180 @@
1
+ """Command hook implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ from agentpool.hooks.base import Hook, HookResult
12
+ from agentpool.log import get_logger
13
+
14
+
15
+ if TYPE_CHECKING:
16
+ from agentpool.hooks.base import HookEvent, HookInput
17
+
18
+
19
+ logger = get_logger(__name__)
20
+
21
+
22
+ class CommandHook(Hook):
23
+ """Hook that executes a shell command.
24
+
25
+ The command receives hook input as JSON via stdin and should return
26
+ JSON output via stdout.
27
+
28
+ Exit codes:
29
+ - 0: Success, stdout parsed as JSON for result
30
+ - 2: Block/deny, stderr used as reason
31
+ - Other: Non-blocking error, logged but execution continues
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ event: HookEvent,
37
+ command: str,
38
+ matcher: str | None = None,
39
+ timeout: float = 60.0,
40
+ enabled: bool = True,
41
+ env: dict[str, str] | None = None,
42
+ ):
43
+ """Initialize command hook.
44
+
45
+ Args:
46
+ event: The lifecycle event this hook handles.
47
+ command: Shell command to execute.
48
+ matcher: Regex pattern for matching.
49
+ timeout: Maximum execution time in seconds.
50
+ enabled: Whether this hook is active.
51
+ env: Additional environment variables.
52
+ """
53
+ super().__init__(event=event, matcher=matcher, timeout=timeout, enabled=enabled)
54
+ self.command = command
55
+ self.env = env or {}
56
+
57
+ async def execute(self, input_data: HookInput) -> HookResult:
58
+ """Execute the shell command.
59
+
60
+ Args:
61
+ input_data: The hook input data, passed as JSON to stdin.
62
+
63
+ Returns:
64
+ Hook result parsed from command output.
65
+ """
66
+ # Prepare environment
67
+ env = os.environ.copy()
68
+ env.update(self.env)
69
+
70
+ # Expand $PROJECT_DIR if present
71
+ command = self.command
72
+ if "$PROJECT_DIR" in command:
73
+ project_dir = env.get("PROJECT_DIR", Path.cwd())
74
+ command = command.replace("$PROJECT_DIR", str(project_dir))
75
+
76
+ # Serialize input
77
+ input_json = json.dumps(dict(input_data))
78
+
79
+ try:
80
+ proc = await asyncio.create_subprocess_shell(
81
+ command,
82
+ stdin=asyncio.subprocess.PIPE,
83
+ stdout=asyncio.subprocess.PIPE,
84
+ stderr=asyncio.subprocess.PIPE,
85
+ env=env,
86
+ )
87
+
88
+ stdout, stderr = await asyncio.wait_for(
89
+ proc.communicate(input_json.encode()),
90
+ timeout=self.timeout,
91
+ )
92
+
93
+ stdout_str = stdout.decode().strip()
94
+ stderr_str = stderr.decode().strip()
95
+
96
+ # Handle exit codes
97
+ if proc.returncode == 0:
98
+ return _parse_success_output(stdout_str)
99
+ if proc.returncode == 2: # noqa: PLR2004
100
+ # Blocking error
101
+ reason = stderr_str or "Hook denied the operation"
102
+ return HookResult(decision="deny", reason=reason)
103
+ # Non-blocking error
104
+ logger.warning("Hook command failed", returncode=proc.returncode, stderr=stderr_str)
105
+ return HookResult(decision="allow")
106
+
107
+ except TimeoutError:
108
+ logger.exception("Hook command timed out", timeout=self.timeout, command=command)
109
+ return HookResult(decision="allow")
110
+ except Exception as e:
111
+ logger.exception("Hook command failed", command=command)
112
+ return HookResult(decision="allow", reason=str(e))
113
+
114
+
115
+ def _parse_success_output(stdout: str) -> HookResult:
116
+ """Parse successful command output.
117
+
118
+ Args:
119
+ stdout: Command stdout.
120
+
121
+ Returns:
122
+ Parsed hook result.
123
+ """
124
+ if not stdout:
125
+ return HookResult(decision="allow")
126
+
127
+ try:
128
+ data = json.loads(stdout)
129
+ return _normalize_result(data)
130
+ except json.JSONDecodeError:
131
+ # Plain text output treated as additional context
132
+ return HookResult(decision="allow", additional_context=stdout)
133
+
134
+
135
+ def _normalize_result(data: dict[str, Any]) -> HookResult:
136
+ """Normalize command output to HookResult.
137
+
138
+ Args:
139
+ data: Parsed JSON data.
140
+
141
+ Returns:
142
+ Normalized hook result.
143
+ """
144
+ result: HookResult = {}
145
+
146
+ # Handle decision field (support various naming conventions)
147
+ decision = data.get("decision") or data.get("permissionDecision")
148
+ if decision:
149
+ # Normalize decision values
150
+ if decision in ("approve", "allow"):
151
+ result["decision"] = "allow"
152
+ elif decision in ("block", "deny"):
153
+ result["decision"] = "deny"
154
+ elif decision == "ask":
155
+ result["decision"] = "ask"
156
+
157
+ # Handle reason field
158
+ reason = data.get("reason") or data.get("permissionDecisionReason")
159
+ if reason:
160
+ result["reason"] = reason
161
+
162
+ # Handle modified input
163
+ if "modified_input" in data:
164
+ result["modified_input"] = data["modified_input"]
165
+ elif "updatedInput" in data:
166
+ result["modified_input"] = data["updatedInput"]
167
+
168
+ # Handle additional context
169
+ if "additional_context" in data:
170
+ result["additional_context"] = data["additional_context"]
171
+ elif "additionalContext" in data:
172
+ result["additional_context"] = data["additionalContext"]
173
+
174
+ # Handle continue flag
175
+ if "continue" in data:
176
+ result["continue_"] = data["continue"]
177
+ elif "continue_" in data:
178
+ result["continue_"] = data["continue_"]
179
+
180
+ return result