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,928 @@
1
+ """Composable message compaction pipeline for managing conversation history.
2
+
3
+ This module provides a pipeline-based approach to compacting and transforming
4
+ pydantic-ai message history. Each step in the pipeline operates on the message
5
+ sequence and can filter, truncate, summarize, or transform messages.
6
+
7
+ Example:
8
+ ```python
9
+ from agentpool.messaging.compaction import (
10
+ CompactionPipeline,
11
+ FilterThinking,
12
+ TruncateToolOutputs,
13
+ KeepLastMessages,
14
+ )
15
+
16
+ # Programmatic usage
17
+ pipeline = CompactionPipeline(steps=[
18
+ FilterThinking(),
19
+ TruncateToolOutputs(max_length=1000),
20
+ KeepLastMessages(count=10),
21
+ ])
22
+ compacted = await pipeline.apply(messages)
23
+
24
+ # Or via config (for YAML)
25
+ config = CompactionPipelineConfig(steps=[
26
+ FilterThinkingConfig(),
27
+ TruncateToolOutputsConfig(max_length=1000),
28
+ KeepLastMessagesConfig(count=10),
29
+ ])
30
+ pipeline = config.build()
31
+ ```
32
+
33
+ YAML configuration example:
34
+ ```yaml
35
+ compaction:
36
+ steps:
37
+ - type: filter_thinking
38
+ - type: truncate_tool_outputs
39
+ max_length: 1000
40
+ - type: keep_last
41
+ count: 10
42
+ - type: summarize
43
+ model: openai:gpt-4o-mini
44
+ threshold: 20
45
+ ```
46
+ """
47
+
48
+ from __future__ import annotations
49
+
50
+ from abc import ABC, abstractmethod
51
+ from collections.abc import Sequence
52
+ from dataclasses import dataclass, field, replace
53
+ from typing import TYPE_CHECKING, Annotated, Any, Literal, Self, cast
54
+
55
+ from pydantic import BaseModel, Field
56
+ from pydantic_ai import (
57
+ Agent,
58
+ BinaryContent,
59
+ ModelRequest,
60
+ ModelResponse,
61
+ RetryPromptPart,
62
+ TextPart,
63
+ ThinkingPart,
64
+ ToolCallPart,
65
+ ToolReturnPart,
66
+ UserPromptPart,
67
+ )
68
+
69
+
70
+ if TYPE_CHECKING:
71
+ from collections.abc import Callable
72
+
73
+ from pydantic_ai import ModelRequestPart, ModelResponsePart
74
+ from tokonomics.model_names import ModelId
75
+
76
+ # Type aliases
77
+ ModelMessage = ModelRequest | ModelResponse
78
+ MessageSequence = Sequence[ModelMessage]
79
+
80
+
81
+ class CompactionStep(ABC):
82
+ """Base class for message compaction steps.
83
+
84
+ Each step transforms a sequence of messages into a (potentially) smaller
85
+ or modified sequence. Steps can be composed into a pipeline.
86
+ """
87
+
88
+ @abstractmethod
89
+ async def apply(self, messages: MessageSequence) -> list[ModelMessage]:
90
+ """Apply this compaction step to the message sequence.
91
+
92
+ Args:
93
+ messages: The input message sequence to transform.
94
+
95
+ Returns:
96
+ The transformed message sequence.
97
+ """
98
+ ...
99
+
100
+ def __or__(self, other: CompactionStep) -> CompactionPipeline:
101
+ """Compose two steps into a pipeline using the | operator."""
102
+ return CompactionPipeline(steps=[self, other])
103
+
104
+
105
+ @dataclass
106
+ class CompactionPipeline(CompactionStep):
107
+ """A pipeline of compaction steps applied in sequence.
108
+
109
+ Steps are applied left-to-right, with each step receiving the output
110
+ of the previous step.
111
+ """
112
+
113
+ steps: list[CompactionStep] = field(default_factory=list)
114
+
115
+ async def apply(self, messages: MessageSequence) -> list[ModelMessage]:
116
+ """Apply all steps in sequence."""
117
+ result: list[ModelMessage] = list(messages)
118
+ for step in self.steps:
119
+ result = await step.apply(result)
120
+ return result
121
+
122
+ def __or__(self, other: CompactionStep) -> CompactionPipeline:
123
+ """Add another step to the pipeline."""
124
+ if isinstance(other, CompactionPipeline):
125
+ return CompactionPipeline(steps=[*self.steps, *other.steps])
126
+ return CompactionPipeline(steps=[*self.steps, other])
127
+
128
+ def __ior__(self, other: CompactionStep) -> Self:
129
+ """Add a step in place."""
130
+ if isinstance(other, CompactionPipeline):
131
+ self.steps.extend(other.steps)
132
+ else:
133
+ self.steps.append(other)
134
+ return self
135
+
136
+
137
+ # =============================================================================
138
+ # Filter Steps - Remove or filter parts of messages
139
+ # =============================================================================
140
+
141
+
142
+ @dataclass
143
+ class FilterThinking(CompactionStep):
144
+ """Remove all thinking parts from model responses.
145
+
146
+ Thinking parts can consume significant context space without providing
147
+ value in subsequent interactions.
148
+ """
149
+
150
+ async def apply(self, messages: MessageSequence) -> list[ModelMessage]:
151
+ result: list[ModelMessage] = []
152
+ for msg in messages:
153
+ match msg:
154
+ case ModelResponse(parts=parts) if any(isinstance(p, ThinkingPart) for p in parts):
155
+ filtered_parts = [p for p in parts if not isinstance(p, ThinkingPart)]
156
+ if filtered_parts: # Only include if there are remaining parts
157
+ result.append(replace(msg, parts=filtered_parts))
158
+ case _:
159
+ result.append(msg)
160
+ return result
161
+
162
+
163
+ @dataclass
164
+ class FilterRetryPrompts(CompactionStep):
165
+ """Remove retry prompt parts from requests.
166
+
167
+ Retry prompts are typically not needed after the conversation has moved on.
168
+ """
169
+
170
+ async def apply(self, messages: MessageSequence) -> list[ModelMessage]:
171
+ result: list[ModelMessage] = []
172
+ for msg in messages:
173
+ match msg:
174
+ case ModelRequest(parts=parts) if any(
175
+ isinstance(p, RetryPromptPart) for p in parts
176
+ ):
177
+ filtered_parts = [p for p in parts if not isinstance(p, RetryPromptPart)]
178
+ if filtered_parts:
179
+ result.append(replace(msg, parts=filtered_parts))
180
+ case _:
181
+ result.append(msg)
182
+ return result
183
+
184
+
185
+ @dataclass
186
+ class FilterBinaryContent(CompactionStep):
187
+ """Remove binary content (images, audio, etc.) from messages.
188
+
189
+ Useful when you want to keep only text content for context efficiency.
190
+ """
191
+
192
+ keep_references: bool = False
193
+ """If True, replace binary with a placeholder text describing what was there."""
194
+
195
+ async def apply(self, messages: MessageSequence) -> list[ModelMessage]:
196
+ result: list[ModelMessage] = []
197
+ for msg in messages:
198
+ match msg:
199
+ case ModelRequest(parts=parts):
200
+ filtered_parts: list[ModelRequestPart | ModelResponsePart] = []
201
+ for part in parts:
202
+ if isinstance(part, UserPromptPart):
203
+ if isinstance(part.content, list):
204
+ new_content: list[Any] = []
205
+ for item in part.content:
206
+ if isinstance(item, BinaryContent):
207
+ if self.keep_references:
208
+ new_content.append(f"[Binary: {item.media_type}]")
209
+ else:
210
+ new_content.append(item)
211
+ if new_content:
212
+ filtered_parts.append(replace(part, content=new_content))
213
+ else:
214
+ filtered_parts.append(part)
215
+ else:
216
+ filtered_parts.append(part)
217
+ if filtered_parts:
218
+ result.append(replace(msg, parts=cast(Sequence[Any], filtered_parts)))
219
+ case _:
220
+ result.append(msg)
221
+ return result
222
+
223
+
224
+ @dataclass
225
+ class FilterToolCalls(CompactionStep):
226
+ """Filter tool calls by name.
227
+
228
+ Can be used to remove specific tool calls that are not relevant
229
+ for future context (e.g., debugging tools, one-time lookups).
230
+ """
231
+
232
+ exclude_tools: list[str] = field(default_factory=list)
233
+ """Tool names to exclude from the history."""
234
+
235
+ include_only: list[str] | None = None
236
+ """If set, only keep these tools (overrides exclude_tools)."""
237
+
238
+ async def apply(self, messages: MessageSequence) -> list[ModelMessage]:
239
+
240
+ def should_keep(tool_name: str) -> bool:
241
+ if self.include_only is not None:
242
+ return tool_name in self.include_only
243
+ return tool_name not in self.exclude_tools
244
+
245
+ result: list[ModelMessage] = []
246
+ excluded_call_ids: set[str] = set()
247
+
248
+ for msg in messages:
249
+ match msg:
250
+ case ModelResponse(parts=parts):
251
+ filtered_parts: list[ModelRequestPart | ModelResponsePart] = []
252
+ for part in parts:
253
+ if isinstance(part, ToolCallPart):
254
+ if should_keep(part.tool_name):
255
+ filtered_parts.append(part)
256
+ else:
257
+ excluded_call_ids.add(part.tool_call_id)
258
+ else:
259
+ filtered_parts.append(part)
260
+ if filtered_parts:
261
+ result.append(replace(msg, parts=cast(Sequence[Any], filtered_parts)))
262
+
263
+ case ModelRequest(parts=parts):
264
+ # Also filter corresponding tool returns
265
+ filtered_parts = [
266
+ p
267
+ for p in parts
268
+ if not (
269
+ isinstance(p, ToolReturnPart) and p.tool_call_id in excluded_call_ids
270
+ )
271
+ ]
272
+ if filtered_parts:
273
+ result.append(replace(msg, parts=cast(Sequence[Any], filtered_parts)))
274
+
275
+ case _:
276
+ result.append(msg)
277
+
278
+ return result
279
+
280
+
281
+ @dataclass
282
+ class FilterEmptyMessages(CompactionStep):
283
+ """Remove messages that have no meaningful content.
284
+
285
+ Cleans up the history by removing empty or near-empty messages.
286
+ """
287
+
288
+ async def apply(self, messages: MessageSequence) -> list[ModelMessage]:
289
+ result: list[ModelMessage] = []
290
+ for msg in messages:
291
+ match msg:
292
+ case ModelRequest(parts=parts) | ModelResponse(parts=parts):
293
+ if any(_part_has_content(p) for p in parts):
294
+ result.append(msg)
295
+ case _:
296
+ result.append(msg)
297
+ return result
298
+
299
+
300
+ def _part_has_content(part: Any) -> bool:
301
+ """Check if a message part has meaningful content."""
302
+ # Use has_content if available (TextPart, ThinkingPart, etc.)
303
+ if hasattr(part, "has_content"):
304
+ return part.has_content() # type: ignore[no-any-return]
305
+ # For UserPromptPart, check content directly
306
+ if isinstance(part, UserPromptPart):
307
+ content = part.content
308
+ if isinstance(content, str):
309
+ return bool(content.strip())
310
+ if isinstance(content, list):
311
+ return bool(content)
312
+ return content is not None
313
+ # For ToolReturnPart, check content
314
+ if isinstance(part, ToolReturnPart):
315
+ return part.content is not None
316
+ # Default: assume has content
317
+ return True
318
+
319
+
320
+ # =============================================================================
321
+ # Truncation Steps - Shorten content while preserving structure
322
+ # =============================================================================
323
+
324
+
325
+ @dataclass
326
+ class TruncateToolOutputs(CompactionStep):
327
+ """Truncate large tool outputs to a maximum length.
328
+
329
+ Tool outputs can sometimes be very large (e.g., file contents, API responses).
330
+ This step truncates them while preserving the beginning of the content.
331
+ """
332
+
333
+ max_length: int = 2000
334
+ """Maximum length for tool output content."""
335
+
336
+ suffix: str = "\n... [truncated]"
337
+ """Suffix to append when content is truncated."""
338
+
339
+ async def apply(self, messages: MessageSequence) -> list[ModelMessage]:
340
+ result: list[ModelMessage] = []
341
+ for msg in messages:
342
+ match msg:
343
+ case ModelRequest(parts=parts):
344
+ new_parts: list[ModelRequestPart | ModelResponsePart] = []
345
+ for part in parts:
346
+ if isinstance(part, ToolReturnPart):
347
+ content = part.content
348
+ if isinstance(content, str) and len(content) > self.max_length:
349
+ truncated = content[: self.max_length - len(self.suffix)]
350
+ new_parts.append(replace(part, content=truncated + self.suffix))
351
+ else:
352
+ new_parts.append(part)
353
+ else:
354
+ new_parts.append(part)
355
+ result.append(replace(msg, parts=cast(Sequence[Any], new_parts)))
356
+ case _:
357
+ result.append(msg)
358
+ return result
359
+
360
+
361
+ @dataclass
362
+ class TruncateTextParts(CompactionStep):
363
+ """Truncate long text parts in responses.
364
+
365
+ Useful for limiting very long model responses in the context.
366
+ """
367
+
368
+ max_length: int = 5000
369
+ """Maximum length for text content."""
370
+
371
+ suffix: str = "\n... [truncated]"
372
+ """Suffix to append when content is truncated."""
373
+
374
+ async def apply(self, messages: MessageSequence) -> list[ModelMessage]:
375
+ result: list[ModelMessage] = []
376
+ for msg in messages:
377
+ match msg:
378
+ case ModelResponse(parts=parts):
379
+ new_parts: list[ModelRequestPart | ModelResponsePart] = []
380
+ for part in parts:
381
+ if isinstance(part, TextPart) and len(part.content) > self.max_length:
382
+ truncated = part.content[: self.max_length - len(self.suffix)]
383
+ new_parts.append(replace(part, content=truncated + self.suffix))
384
+ else:
385
+ new_parts.append(part)
386
+ result.append(replace(msg, parts=cast(Sequence[Any], new_parts)))
387
+ case _:
388
+ result.append(msg)
389
+ return result
390
+
391
+
392
+ # =============================================================================
393
+ # Selection Steps - Keep subsets of messages
394
+ # =============================================================================
395
+
396
+
397
+ @dataclass
398
+ class KeepLastMessages(CompactionStep):
399
+ """Keep only the last N messages.
400
+
401
+ A simple sliding window approach to context management.
402
+ Messages are counted as request/response pairs when `count_pairs` is True.
403
+ """
404
+
405
+ count: int = 10
406
+ """Number of messages (or pairs) to keep."""
407
+
408
+ count_pairs: bool = True
409
+ """If True, count request/response pairs instead of individual messages."""
410
+
411
+ async def apply(self, messages: MessageSequence) -> list[ModelMessage]:
412
+ if not messages:
413
+ return []
414
+
415
+ if not self.count_pairs:
416
+ return list(messages[-self.count :])
417
+
418
+ # Count pairs (each request+response = 1 pair)
419
+ pairs: list[list[ModelMessage]] = []
420
+ current_pair: list[ModelMessage] = []
421
+
422
+ for msg in messages:
423
+ current_pair.append(msg)
424
+ if isinstance(msg, ModelResponse):
425
+ pairs.append(current_pair)
426
+ current_pair = []
427
+
428
+ # Don't forget incomplete pair at the end
429
+ if current_pair:
430
+ pairs.append(current_pair)
431
+
432
+ # Keep last N pairs
433
+ kept_pairs = pairs[-self.count :]
434
+ return [msg for pair in kept_pairs for msg in pair]
435
+
436
+
437
+ @dataclass
438
+ class KeepFirstMessages(CompactionStep):
439
+ """Keep only the first N messages.
440
+
441
+ Useful for keeping initial context/instructions while discarding
442
+ middle conversation.
443
+ """
444
+
445
+ count: int = 2
446
+ """Number of messages to keep from the beginning."""
447
+
448
+ async def apply(self, messages: MessageSequence) -> list[ModelMessage]:
449
+ return list(messages[: self.count])
450
+
451
+
452
+ @dataclass
453
+ class KeepFirstAndLast(CompactionStep):
454
+ """Keep first N and last M messages, discarding the middle.
455
+
456
+ Useful for preserving initial context while maintaining recent history.
457
+ """
458
+
459
+ first_count: int = 2
460
+ """Number of messages to keep from the beginning."""
461
+
462
+ last_count: int = 5
463
+ """Number of messages to keep from the end."""
464
+
465
+ async def apply(self, messages: MessageSequence) -> list[ModelMessage]:
466
+ msg_list = list(messages)
467
+ if len(msg_list) <= self.first_count + self.last_count:
468
+ return msg_list
469
+
470
+ first = msg_list[: self.first_count]
471
+ last = msg_list[-self.last_count :]
472
+ return first + last
473
+
474
+
475
+ # =============================================================================
476
+ # Advanced Steps - Token-aware and LLM-based
477
+ # =============================================================================
478
+
479
+
480
+ @dataclass
481
+ class TokenBudget(CompactionStep):
482
+ """Keep messages that fit within a token budget.
483
+
484
+ Works backwards from most recent, adding messages until the budget
485
+ is exhausted. Requires tokonomics for token counting.
486
+ """
487
+
488
+ max_tokens: int = 4000
489
+ """Maximum number of tokens to allow."""
490
+
491
+ model: str = "gpt-4o"
492
+ """Model to use for token counting."""
493
+
494
+ async def apply(self, messages: MessageSequence) -> list[ModelMessage]:
495
+ try:
496
+ import tokonomics
497
+ except ImportError:
498
+ # Fall back to character-based estimation
499
+ return await self._apply_char_estimate(messages)
500
+
501
+ result: list[ModelMessage] = []
502
+ total_tokens = 0
503
+
504
+ # Process from most recent to oldest
505
+ for msg in reversed(messages):
506
+ # Estimate tokens for this message
507
+ text = _extract_text_content(msg)
508
+ token_count = tokonomics.count_tokens(text, self.model)
509
+
510
+ if total_tokens + token_count > self.max_tokens:
511
+ break
512
+
513
+ result.insert(0, msg)
514
+ total_tokens += token_count
515
+
516
+ return result
517
+
518
+ async def _apply_char_estimate(self, messages: MessageSequence) -> list[ModelMessage]:
519
+ """Fallback using character-based token estimation (4 chars ≈ 1 token)."""
520
+ result: list[ModelMessage] = []
521
+ total_chars = 0
522
+ max_chars = self.max_tokens * 4
523
+
524
+ for msg in reversed(messages):
525
+ text = _extract_text_content(msg)
526
+ char_count = len(text)
527
+
528
+ if total_chars + char_count > max_chars:
529
+ break
530
+
531
+ result.insert(0, msg)
532
+ total_chars += char_count
533
+
534
+ return result
535
+
536
+
537
+ @dataclass
538
+ class Summarize(CompactionStep):
539
+ """Summarize older messages using an LLM.
540
+
541
+ When the message count exceeds the threshold, older messages are
542
+ summarized into a single message while recent ones are kept intact.
543
+ """
544
+
545
+ model: str = "openai:gpt-4o-mini"
546
+ """Model to use for summarization."""
547
+
548
+ threshold: int = 15
549
+ """Minimum message count before summarization kicks in."""
550
+
551
+ keep_recent: int = 5
552
+ """Number of recent messages to keep unsummarized."""
553
+
554
+ summary_prompt: str = (
555
+ "Summarize the following conversation history concisely, "
556
+ "preserving key information, decisions, and context that may be "
557
+ "relevant for continuing the conversation:\n\n{conversation}"
558
+ )
559
+ """Prompt template for summarization. Use {conversation} placeholder."""
560
+
561
+ _agent: Agent[None, str] | None = field(default=None, repr=False)
562
+
563
+ async def apply(self, messages: MessageSequence) -> list[ModelMessage]:
564
+ if len(messages) <= self.threshold:
565
+ return list(messages)
566
+
567
+ # Split into messages to summarize and messages to keep
568
+ to_summarize = list(messages[: -self.keep_recent])
569
+ to_keep = list(messages[-self.keep_recent :])
570
+
571
+ # Format conversation for summarization
572
+ conversation_text = _format_conversation(to_summarize)
573
+
574
+ # Get or create summarization agent
575
+ agent = await self._get_agent()
576
+
577
+ # Generate summary
578
+ prompt = self.summary_prompt.format(conversation=conversation_text)
579
+ result = await agent.run(prompt)
580
+
581
+ # Create summary message
582
+ summary_request = ModelRequest(
583
+ parts=[UserPromptPart(content=f"[Conversation Summary]\n{result.output}")]
584
+ )
585
+
586
+ return [summary_request, *to_keep]
587
+
588
+ async def _get_agent(self) -> Agent[None, str]:
589
+ """Get or create the summarization agent."""
590
+ if self._agent is None:
591
+ self._agent = Agent(model=self.model, output_type=str)
592
+ return self._agent
593
+
594
+
595
+ # =============================================================================
596
+ # Conditional Steps - Apply steps based on conditions
597
+ # =============================================================================
598
+
599
+
600
+ @dataclass
601
+ class ConditionalStep(CompactionStep):
602
+ """Apply a step only when a condition is met."""
603
+
604
+ step: CompactionStep
605
+ """The step to conditionally apply."""
606
+
607
+ condition: Callable[[MessageSequence], bool]
608
+ """Function that returns True if the step should be applied."""
609
+
610
+ async def apply(self, messages: MessageSequence) -> list[ModelMessage]:
611
+ if self.condition(messages):
612
+ return await self.step.apply(messages)
613
+ return list(messages)
614
+
615
+
616
+ @dataclass
617
+ class WhenMessageCountExceeds(CompactionStep):
618
+ """Apply a step only when message count exceeds a threshold."""
619
+
620
+ step: CompactionStep
621
+ """The step to conditionally apply."""
622
+
623
+ threshold: int = 20
624
+ """Message count threshold."""
625
+
626
+ async def apply(self, messages: MessageSequence) -> list[ModelMessage]:
627
+ if len(messages) > self.threshold:
628
+ return await self.step.apply(messages)
629
+ return list(messages)
630
+
631
+
632
+ # =============================================================================
633
+ # Configuration Models - For YAML/JSON configuration
634
+ # =============================================================================
635
+
636
+
637
+ class FilterThinkingConfig(BaseModel):
638
+ """Configuration for FilterThinking step."""
639
+
640
+ type: Literal["filter_thinking"] = "filter_thinking"
641
+
642
+ def build(self) -> FilterThinking:
643
+ return FilterThinking()
644
+
645
+
646
+ class FilterRetryPromptsConfig(BaseModel):
647
+ """Configuration for FilterRetryPrompts step."""
648
+
649
+ type: Literal["filter_retry_prompts"] = "filter_retry_prompts"
650
+
651
+ def build(self) -> FilterRetryPrompts:
652
+ return FilterRetryPrompts()
653
+
654
+
655
+ class FilterBinaryContentConfig(BaseModel):
656
+ """Configuration for FilterBinaryContent step."""
657
+
658
+ type: Literal["filter_binary"] = "filter_binary"
659
+ keep_references: bool = False
660
+
661
+ def build(self) -> FilterBinaryContent:
662
+ return FilterBinaryContent(keep_references=self.keep_references)
663
+
664
+
665
+ class FilterToolCallsConfig(BaseModel):
666
+ """Configuration for FilterToolCalls step."""
667
+
668
+ type: Literal["filter_tools"] = "filter_tools"
669
+ exclude_tools: list[str] = Field(default_factory=list)
670
+ include_only: list[str] | None = None
671
+
672
+ def build(self) -> FilterToolCalls:
673
+ return FilterToolCalls(exclude_tools=self.exclude_tools, include_only=self.include_only)
674
+
675
+
676
+ class FilterEmptyMessagesConfig(BaseModel):
677
+ """Configuration for FilterEmptyMessages step."""
678
+
679
+ type: Literal["filter_empty"] = "filter_empty"
680
+
681
+ def build(self) -> FilterEmptyMessages:
682
+ return FilterEmptyMessages()
683
+
684
+
685
+ class TruncateToolOutputsConfig(BaseModel):
686
+ """Configuration for TruncateToolOutputs step."""
687
+
688
+ type: Literal["truncate_tool_outputs"] = "truncate_tool_outputs"
689
+ max_length: int = 2000
690
+ suffix: str = "\n... [truncated]"
691
+
692
+ def build(self) -> TruncateToolOutputs:
693
+ return TruncateToolOutputs(max_length=self.max_length, suffix=self.suffix)
694
+
695
+
696
+ class TruncateTextPartsConfig(BaseModel):
697
+ """Configuration for TruncateTextParts step."""
698
+
699
+ type: Literal["truncate_text"] = "truncate_text"
700
+ max_length: int = 5000
701
+ suffix: str = "\n... [truncated]"
702
+
703
+ def build(self) -> TruncateTextParts:
704
+ return TruncateTextParts(max_length=self.max_length, suffix=self.suffix)
705
+
706
+
707
+ class KeepLastMessagesConfig(BaseModel):
708
+ """Configuration for KeepLastMessages step."""
709
+
710
+ type: Literal["keep_last"] = "keep_last"
711
+ count: int = 10
712
+ count_pairs: bool = True
713
+
714
+ def build(self) -> KeepLastMessages:
715
+ return KeepLastMessages(count=self.count, count_pairs=self.count_pairs)
716
+
717
+
718
+ class KeepFirstMessagesConfig(BaseModel):
719
+ """Configuration for KeepFirstMessages step."""
720
+
721
+ type: Literal["keep_first"] = "keep_first"
722
+ count: int = 2
723
+
724
+ def build(self) -> KeepFirstMessages:
725
+ return KeepFirstMessages(count=self.count)
726
+
727
+
728
+ class KeepFirstAndLastConfig(BaseModel):
729
+ """Configuration for KeepFirstAndLast step."""
730
+
731
+ type: Literal["keep_first_last"] = "keep_first_last"
732
+ first_count: int = 2
733
+ last_count: int = 5
734
+
735
+ def build(self) -> KeepFirstAndLast:
736
+ return KeepFirstAndLast(first_count=self.first_count, last_count=self.last_count)
737
+
738
+
739
+ class TokenBudgetConfig(BaseModel):
740
+ """Configuration for TokenBudget step."""
741
+
742
+ type: Literal["token_budget"] = "token_budget"
743
+ max_tokens: int = 4000
744
+ model: str = "gpt-4o"
745
+
746
+ def build(self) -> TokenBudget:
747
+ return TokenBudget(max_tokens=self.max_tokens, model=self.model)
748
+
749
+
750
+ class SummarizeConfig(BaseModel):
751
+ """Configuration for Summarize step."""
752
+
753
+ type: Literal["summarize"] = "summarize"
754
+ model: str = "openai:gpt-4o-mini"
755
+ threshold: int = 15
756
+ keep_recent: int = 5
757
+ summary_prompt: str | None = None
758
+
759
+ def build(self) -> Summarize:
760
+ kwargs: dict[str, Any] = {
761
+ "model": self.model,
762
+ "threshold": self.threshold,
763
+ "keep_recent": self.keep_recent,
764
+ }
765
+ if self.summary_prompt:
766
+ kwargs["summary_prompt"] = self.summary_prompt
767
+ return Summarize(**kwargs)
768
+
769
+
770
+ class WhenMessageCountExceedsConfig(BaseModel):
771
+ """Configuration for WhenMessageCountExceeds wrapper."""
772
+
773
+ type: Literal["when_count_exceeds"] = "when_count_exceeds"
774
+ threshold: int = 20
775
+ step: "CompactionStepConfig"
776
+
777
+ def build(self) -> WhenMessageCountExceeds:
778
+ return WhenMessageCountExceeds(step=self.step.build(), threshold=self.threshold)
779
+
780
+
781
+ # Union of all config types with discriminator
782
+ CompactionStepConfig = Annotated[
783
+ FilterThinkingConfig
784
+ | FilterRetryPromptsConfig
785
+ | FilterBinaryContentConfig
786
+ | FilterToolCallsConfig
787
+ | FilterEmptyMessagesConfig
788
+ | TruncateToolOutputsConfig
789
+ | TruncateTextPartsConfig
790
+ | KeepLastMessagesConfig
791
+ | KeepFirstMessagesConfig
792
+ | KeepFirstAndLastConfig
793
+ | TokenBudgetConfig
794
+ | SummarizeConfig
795
+ | WhenMessageCountExceedsConfig,
796
+ Field(discriminator="type"),
797
+ ]
798
+
799
+ # Update forward reference
800
+ WhenMessageCountExceedsConfig.model_rebuild()
801
+
802
+
803
+ class CompactionPipelineConfig(BaseModel):
804
+ """Configuration for a complete compaction pipeline.
805
+
806
+ Example YAML:
807
+ ```yaml
808
+ compaction:
809
+ steps:
810
+ - type: filter_thinking
811
+ - type: truncate_tool_outputs
812
+ max_length: 1000
813
+ - type: keep_last
814
+ count: 10
815
+ ```
816
+ """
817
+
818
+ steps: list[CompactionStepConfig] = Field(default_factory=list)
819
+ """Ordered list of compaction steps to apply."""
820
+
821
+ def build(self) -> CompactionPipeline:
822
+ """Build a CompactionPipeline from this configuration."""
823
+ return CompactionPipeline(steps=[step.build() for step in self.steps])
824
+
825
+
826
+ # =============================================================================
827
+ # Preset Pipelines - Common configurations
828
+ # =============================================================================
829
+
830
+
831
+ def minimal_context() -> CompactionPipeline:
832
+ """Create a pipeline that aggressively minimizes context.
833
+
834
+ Removes thinking, truncates outputs, and keeps only recent messages.
835
+ """
836
+ return CompactionPipeline(
837
+ steps=[
838
+ FilterThinking(),
839
+ FilterRetryPrompts(),
840
+ TruncateToolOutputs(max_length=500),
841
+ KeepLastMessages(count=5),
842
+ ]
843
+ )
844
+
845
+
846
+ def balanced_context() -> CompactionPipeline:
847
+ """Create a balanced pipeline for general use.
848
+
849
+ Removes thinking, moderately truncates, keeps reasonable history.
850
+ """
851
+ return CompactionPipeline(
852
+ steps=[
853
+ FilterThinking(),
854
+ TruncateToolOutputs(max_length=2000),
855
+ TruncateTextParts(max_length=5000),
856
+ KeepLastMessages(count=15),
857
+ ]
858
+ )
859
+
860
+
861
+ def summarizing_context(model: ModelId | str = "openai:gpt-4o-mini") -> CompactionPipeline:
862
+ """Create a pipeline that summarizes older messages.
863
+
864
+ Best for long conversations where context needs to be preserved.
865
+ """
866
+ return CompactionPipeline(
867
+ steps=[
868
+ FilterThinking(),
869
+ TruncateToolOutputs(max_length=1000),
870
+ Summarize(model=model, threshold=20, keep_recent=8),
871
+ ]
872
+ )
873
+
874
+
875
+ # =============================================================================
876
+ # Helper Functions
877
+ # =============================================================================
878
+
879
+
880
+ def _extract_text_content(msg: ModelMessage) -> str:
881
+ """Extract text content from a message for token counting."""
882
+ parts_text: list[str] = []
883
+
884
+ match msg:
885
+ case ModelRequest(parts=parts) | ModelResponse(parts=parts):
886
+ for part in parts:
887
+ match part:
888
+ case TextPart(content=content):
889
+ parts_text.append(content)
890
+ case ThinkingPart(content=content):
891
+ parts_text.append(content)
892
+ case UserPromptPart(content=content):
893
+ if isinstance(content, str):
894
+ parts_text.append(content)
895
+ elif isinstance(content, list):
896
+ for item in content:
897
+ if isinstance(item, str):
898
+ parts_text.append(item) # noqa: PERF401
899
+ case ToolReturnPart(content=content):
900
+ if isinstance(content, str):
901
+ parts_text.append(content)
902
+ elif content is not None:
903
+ parts_text.append(str(content))
904
+
905
+ return "\n".join(parts_text)
906
+
907
+
908
+ def _format_conversation(messages: Sequence[ModelMessage]) -> str:
909
+ """Format messages as a readable conversation for summarization."""
910
+ lines: list[str] = []
911
+
912
+ for msg in messages:
913
+ match msg:
914
+ case ModelRequest(parts=parts):
915
+ for request_part in parts:
916
+ match request_part:
917
+ case UserPromptPart(content=content):
918
+ text = content if isinstance(content, str) else str(content)
919
+ lines.append(f"User: {text}")
920
+ case ToolReturnPart(tool_name=name, content=content):
921
+ lines.append(f"Tool Result ({name}): {content}")
922
+ case ModelResponse(parts=parts):
923
+ for response_part in parts:
924
+ match response_part:
925
+ case TextPart(content=content):
926
+ lines.append(f"Assistant: {content}")
927
+
928
+ return "\n\n".join(lines)