agentpool 2.1.9__py3-none-any.whl → 2.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. acp/__init__.py +13 -4
  2. acp/acp_requests.py +20 -77
  3. acp/agent/connection.py +8 -0
  4. acp/agent/implementations/debug_server/debug_server.py +6 -2
  5. acp/agent/protocol.py +6 -0
  6. acp/bridge/README.md +15 -2
  7. acp/bridge/__init__.py +3 -2
  8. acp/bridge/__main__.py +60 -19
  9. acp/bridge/ws_server.py +173 -0
  10. acp/bridge/ws_server_cli.py +89 -0
  11. acp/client/connection.py +38 -29
  12. acp/client/implementations/default_client.py +3 -2
  13. acp/client/implementations/headless_client.py +2 -2
  14. acp/connection.py +2 -2
  15. acp/notifications.py +20 -50
  16. acp/schema/__init__.py +2 -0
  17. acp/schema/agent_responses.py +21 -0
  18. acp/schema/client_requests.py +3 -3
  19. acp/schema/session_state.py +63 -29
  20. acp/stdio.py +39 -9
  21. acp/task/supervisor.py +2 -2
  22. acp/transports.py +362 -2
  23. acp/utils.py +17 -4
  24. agentpool/__init__.py +6 -1
  25. agentpool/agents/__init__.py +2 -0
  26. agentpool/agents/acp_agent/acp_agent.py +407 -277
  27. agentpool/agents/acp_agent/acp_converters.py +196 -38
  28. agentpool/agents/acp_agent/client_handler.py +191 -26
  29. agentpool/agents/acp_agent/session_state.py +17 -6
  30. agentpool/agents/agent.py +607 -572
  31. agentpool/agents/agui_agent/__init__.py +0 -2
  32. agentpool/agents/agui_agent/agui_agent.py +176 -110
  33. agentpool/agents/agui_agent/agui_converters.py +0 -131
  34. agentpool/agents/agui_agent/helpers.py +3 -4
  35. agentpool/agents/base_agent.py +632 -17
  36. agentpool/agents/claude_code_agent/FORKING.md +191 -0
  37. agentpool/agents/claude_code_agent/__init__.py +13 -1
  38. agentpool/agents/claude_code_agent/claude_code_agent.py +1058 -291
  39. agentpool/agents/claude_code_agent/converters.py +74 -143
  40. agentpool/agents/claude_code_agent/history.py +474 -0
  41. agentpool/agents/claude_code_agent/models.py +77 -0
  42. agentpool/agents/claude_code_agent/static_info.py +100 -0
  43. agentpool/agents/claude_code_agent/usage.py +242 -0
  44. agentpool/agents/context.py +40 -0
  45. agentpool/agents/events/__init__.py +24 -0
  46. agentpool/agents/events/builtin_handlers.py +67 -1
  47. agentpool/agents/events/event_emitter.py +32 -2
  48. agentpool/agents/events/events.py +104 -3
  49. agentpool/agents/events/infer_info.py +145 -0
  50. agentpool/agents/events/processors.py +254 -0
  51. agentpool/agents/interactions.py +41 -6
  52. agentpool/agents/modes.py +67 -0
  53. agentpool/agents/slashed_agent.py +5 -4
  54. agentpool/agents/tool_call_accumulator.py +213 -0
  55. agentpool/agents/tool_wrapping.py +18 -6
  56. agentpool/common_types.py +56 -21
  57. agentpool/config_resources/__init__.py +38 -1
  58. agentpool/config_resources/acp_assistant.yml +2 -2
  59. agentpool/config_resources/agents.yml +3 -0
  60. agentpool/config_resources/agents_template.yml +1 -0
  61. agentpool/config_resources/claude_code_agent.yml +10 -6
  62. agentpool/config_resources/external_acp_agents.yml +2 -1
  63. agentpool/delegation/base_team.py +4 -30
  64. agentpool/delegation/pool.py +136 -289
  65. agentpool/delegation/team.py +58 -57
  66. agentpool/delegation/teamrun.py +51 -55
  67. agentpool/diagnostics/__init__.py +53 -0
  68. agentpool/diagnostics/lsp_manager.py +1593 -0
  69. agentpool/diagnostics/lsp_proxy.py +41 -0
  70. agentpool/diagnostics/lsp_proxy_script.py +229 -0
  71. agentpool/diagnostics/models.py +398 -0
  72. agentpool/functional/run.py +10 -4
  73. agentpool/mcp_server/__init__.py +0 -2
  74. agentpool/mcp_server/client.py +76 -32
  75. agentpool/mcp_server/conversions.py +54 -13
  76. agentpool/mcp_server/manager.py +34 -54
  77. agentpool/mcp_server/registries/official_registry_client.py +35 -1
  78. agentpool/mcp_server/tool_bridge.py +186 -139
  79. agentpool/messaging/__init__.py +0 -2
  80. agentpool/messaging/compaction.py +72 -197
  81. agentpool/messaging/connection_manager.py +11 -10
  82. agentpool/messaging/event_manager.py +5 -5
  83. agentpool/messaging/message_container.py +6 -30
  84. agentpool/messaging/message_history.py +99 -8
  85. agentpool/messaging/messagenode.py +52 -14
  86. agentpool/messaging/messages.py +54 -35
  87. agentpool/messaging/processing.py +12 -22
  88. agentpool/models/__init__.py +1 -1
  89. agentpool/models/acp_agents/base.py +6 -24
  90. agentpool/models/acp_agents/mcp_capable.py +126 -157
  91. agentpool/models/acp_agents/non_mcp.py +129 -95
  92. agentpool/models/agents.py +98 -76
  93. agentpool/models/agui_agents.py +1 -1
  94. agentpool/models/claude_code_agents.py +144 -19
  95. agentpool/models/file_parsing.py +0 -1
  96. agentpool/models/manifest.py +113 -50
  97. agentpool/prompts/conversion_manager.py +1 -1
  98. agentpool/prompts/prompts.py +5 -2
  99. agentpool/repomap.py +1 -1
  100. agentpool/resource_providers/__init__.py +11 -1
  101. agentpool/resource_providers/aggregating.py +56 -5
  102. agentpool/resource_providers/base.py +70 -4
  103. agentpool/resource_providers/codemode/code_executor.py +72 -5
  104. agentpool/resource_providers/codemode/helpers.py +2 -2
  105. agentpool/resource_providers/codemode/provider.py +64 -12
  106. agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
  107. agentpool/resource_providers/codemode/remote_provider.py +9 -12
  108. agentpool/resource_providers/filtering.py +3 -1
  109. agentpool/resource_providers/mcp_provider.py +89 -12
  110. agentpool/resource_providers/plan_provider.py +228 -46
  111. agentpool/resource_providers/pool.py +7 -3
  112. agentpool/resource_providers/resource_info.py +111 -0
  113. agentpool/resource_providers/static.py +4 -2
  114. agentpool/sessions/__init__.py +4 -1
  115. agentpool/sessions/manager.py +33 -5
  116. agentpool/sessions/models.py +59 -6
  117. agentpool/sessions/protocol.py +28 -0
  118. agentpool/sessions/session.py +11 -55
  119. agentpool/skills/registry.py +13 -8
  120. agentpool/storage/manager.py +572 -49
  121. agentpool/talk/registry.py +4 -4
  122. agentpool/talk/talk.py +9 -10
  123. agentpool/testing.py +538 -20
  124. agentpool/tool_impls/__init__.py +6 -0
  125. agentpool/tool_impls/agent_cli/__init__.py +42 -0
  126. agentpool/tool_impls/agent_cli/tool.py +95 -0
  127. agentpool/tool_impls/bash/__init__.py +64 -0
  128. agentpool/tool_impls/bash/helpers.py +35 -0
  129. agentpool/tool_impls/bash/tool.py +171 -0
  130. agentpool/tool_impls/delete_path/__init__.py +70 -0
  131. agentpool/tool_impls/delete_path/tool.py +142 -0
  132. agentpool/tool_impls/download_file/__init__.py +80 -0
  133. agentpool/tool_impls/download_file/tool.py +183 -0
  134. agentpool/tool_impls/execute_code/__init__.py +55 -0
  135. agentpool/tool_impls/execute_code/tool.py +163 -0
  136. agentpool/tool_impls/grep/__init__.py +80 -0
  137. agentpool/tool_impls/grep/tool.py +200 -0
  138. agentpool/tool_impls/list_directory/__init__.py +73 -0
  139. agentpool/tool_impls/list_directory/tool.py +197 -0
  140. agentpool/tool_impls/question/__init__.py +42 -0
  141. agentpool/tool_impls/question/tool.py +127 -0
  142. agentpool/tool_impls/read/__init__.py +104 -0
  143. agentpool/tool_impls/read/tool.py +305 -0
  144. agentpool/tools/__init__.py +2 -1
  145. agentpool/tools/base.py +114 -34
  146. agentpool/tools/manager.py +57 -1
  147. agentpool/ui/base.py +2 -2
  148. agentpool/ui/mock_provider.py +2 -2
  149. agentpool/ui/stdlib_provider.py +2 -2
  150. agentpool/utils/file_watcher.py +269 -0
  151. agentpool/utils/identifiers.py +121 -0
  152. agentpool/utils/pydantic_ai_helpers.py +46 -0
  153. agentpool/utils/streams.py +616 -2
  154. agentpool/utils/subprocess_utils.py +155 -0
  155. agentpool/utils/token_breakdown.py +461 -0
  156. agentpool/vfs_registry.py +7 -2
  157. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
  158. agentpool-2.5.0.dist-info/RECORD +579 -0
  159. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
  160. agentpool_cli/__main__.py +24 -0
  161. agentpool_cli/create.py +1 -1
  162. agentpool_cli/serve_acp.py +100 -21
  163. agentpool_cli/serve_agui.py +87 -0
  164. agentpool_cli/serve_opencode.py +119 -0
  165. agentpool_cli/ui.py +557 -0
  166. agentpool_commands/__init__.py +42 -5
  167. agentpool_commands/agents.py +75 -2
  168. agentpool_commands/history.py +62 -0
  169. agentpool_commands/mcp.py +176 -0
  170. agentpool_commands/models.py +56 -3
  171. agentpool_commands/pool.py +260 -0
  172. agentpool_commands/session.py +1 -1
  173. agentpool_commands/text_sharing/__init__.py +119 -0
  174. agentpool_commands/text_sharing/base.py +123 -0
  175. agentpool_commands/text_sharing/github_gist.py +80 -0
  176. agentpool_commands/text_sharing/opencode.py +462 -0
  177. agentpool_commands/text_sharing/paste_rs.py +59 -0
  178. agentpool_commands/text_sharing/pastebin.py +116 -0
  179. agentpool_commands/text_sharing/shittycodingagent.py +112 -0
  180. agentpool_commands/tools.py +57 -0
  181. agentpool_commands/utils.py +80 -30
  182. agentpool_config/__init__.py +30 -2
  183. agentpool_config/agentpool_tools.py +498 -0
  184. agentpool_config/builtin_tools.py +77 -22
  185. agentpool_config/commands.py +24 -1
  186. agentpool_config/compaction.py +258 -0
  187. agentpool_config/converters.py +1 -1
  188. agentpool_config/event_handlers.py +42 -0
  189. agentpool_config/events.py +1 -1
  190. agentpool_config/forward_targets.py +1 -4
  191. agentpool_config/jinja.py +3 -3
  192. agentpool_config/mcp_server.py +132 -6
  193. agentpool_config/nodes.py +1 -1
  194. agentpool_config/observability.py +44 -0
  195. agentpool_config/session.py +0 -3
  196. agentpool_config/storage.py +82 -38
  197. agentpool_config/task.py +3 -3
  198. agentpool_config/tools.py +11 -22
  199. agentpool_config/toolsets.py +109 -233
  200. agentpool_server/a2a_server/agent_worker.py +307 -0
  201. agentpool_server/a2a_server/server.py +23 -18
  202. agentpool_server/acp_server/acp_agent.py +234 -181
  203. agentpool_server/acp_server/commands/acp_commands.py +151 -156
  204. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
  205. agentpool_server/acp_server/event_converter.py +651 -0
  206. agentpool_server/acp_server/input_provider.py +53 -10
  207. agentpool_server/acp_server/server.py +24 -90
  208. agentpool_server/acp_server/session.py +173 -331
  209. agentpool_server/acp_server/session_manager.py +8 -34
  210. agentpool_server/agui_server/server.py +3 -1
  211. agentpool_server/mcp_server/server.py +5 -2
  212. agentpool_server/opencode_server/.rules +95 -0
  213. agentpool_server/opencode_server/ENDPOINTS.md +401 -0
  214. agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
  215. agentpool_server/opencode_server/__init__.py +19 -0
  216. agentpool_server/opencode_server/command_validation.py +172 -0
  217. agentpool_server/opencode_server/converters.py +975 -0
  218. agentpool_server/opencode_server/dependencies.py +24 -0
  219. agentpool_server/opencode_server/input_provider.py +421 -0
  220. agentpool_server/opencode_server/models/__init__.py +250 -0
  221. agentpool_server/opencode_server/models/agent.py +53 -0
  222. agentpool_server/opencode_server/models/app.py +72 -0
  223. agentpool_server/opencode_server/models/base.py +26 -0
  224. agentpool_server/opencode_server/models/common.py +23 -0
  225. agentpool_server/opencode_server/models/config.py +37 -0
  226. agentpool_server/opencode_server/models/events.py +821 -0
  227. agentpool_server/opencode_server/models/file.py +88 -0
  228. agentpool_server/opencode_server/models/mcp.py +44 -0
  229. agentpool_server/opencode_server/models/message.py +179 -0
  230. agentpool_server/opencode_server/models/parts.py +323 -0
  231. agentpool_server/opencode_server/models/provider.py +81 -0
  232. agentpool_server/opencode_server/models/pty.py +43 -0
  233. agentpool_server/opencode_server/models/question.py +56 -0
  234. agentpool_server/opencode_server/models/session.py +111 -0
  235. agentpool_server/opencode_server/routes/__init__.py +29 -0
  236. agentpool_server/opencode_server/routes/agent_routes.py +473 -0
  237. agentpool_server/opencode_server/routes/app_routes.py +202 -0
  238. agentpool_server/opencode_server/routes/config_routes.py +302 -0
  239. agentpool_server/opencode_server/routes/file_routes.py +571 -0
  240. agentpool_server/opencode_server/routes/global_routes.py +94 -0
  241. agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
  242. agentpool_server/opencode_server/routes/message_routes.py +761 -0
  243. agentpool_server/opencode_server/routes/permission_routes.py +63 -0
  244. agentpool_server/opencode_server/routes/pty_routes.py +300 -0
  245. agentpool_server/opencode_server/routes/question_routes.py +128 -0
  246. agentpool_server/opencode_server/routes/session_routes.py +1276 -0
  247. agentpool_server/opencode_server/routes/tui_routes.py +139 -0
  248. agentpool_server/opencode_server/server.py +475 -0
  249. agentpool_server/opencode_server/state.py +151 -0
  250. agentpool_server/opencode_server/time_utils.py +8 -0
  251. agentpool_storage/__init__.py +12 -0
  252. agentpool_storage/base.py +184 -2
  253. agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
  254. agentpool_storage/claude_provider/__init__.py +42 -0
  255. agentpool_storage/claude_provider/provider.py +1089 -0
  256. agentpool_storage/file_provider.py +278 -15
  257. agentpool_storage/memory_provider.py +193 -12
  258. agentpool_storage/models.py +3 -0
  259. agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
  260. agentpool_storage/opencode_provider/__init__.py +16 -0
  261. agentpool_storage/opencode_provider/helpers.py +414 -0
  262. agentpool_storage/opencode_provider/provider.py +895 -0
  263. agentpool_storage/project_store.py +325 -0
  264. agentpool_storage/session_store.py +26 -6
  265. agentpool_storage/sql_provider/__init__.py +4 -2
  266. agentpool_storage/sql_provider/models.py +48 -0
  267. agentpool_storage/sql_provider/sql_provider.py +269 -3
  268. agentpool_storage/sql_provider/utils.py +12 -13
  269. agentpool_storage/zed_provider/__init__.py +16 -0
  270. agentpool_storage/zed_provider/helpers.py +281 -0
  271. agentpool_storage/zed_provider/models.py +130 -0
  272. agentpool_storage/zed_provider/provider.py +442 -0
  273. agentpool_storage/zed_provider.py +803 -0
  274. agentpool_toolsets/__init__.py +0 -2
  275. agentpool_toolsets/builtin/__init__.py +2 -12
  276. agentpool_toolsets/builtin/code.py +96 -57
  277. agentpool_toolsets/builtin/debug.py +118 -48
  278. agentpool_toolsets/builtin/execution_environment.py +115 -230
  279. agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
  280. agentpool_toolsets/builtin/skills.py +9 -4
  281. agentpool_toolsets/builtin/subagent_tools.py +64 -51
  282. agentpool_toolsets/builtin/workers.py +4 -2
  283. agentpool_toolsets/composio_toolset.py +2 -2
  284. agentpool_toolsets/entry_points.py +3 -1
  285. agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
  286. agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
  287. agentpool_toolsets/fsspec_toolset/grep.py +99 -7
  288. agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
  289. agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
  290. agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
  291. agentpool_toolsets/mcp_discovery/__init__.py +5 -0
  292. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  293. agentpool_toolsets/mcp_discovery/toolset.py +511 -0
  294. agentpool_toolsets/mcp_run_toolset.py +87 -12
  295. agentpool_toolsets/notifications.py +33 -33
  296. agentpool_toolsets/openapi.py +3 -1
  297. agentpool_toolsets/search_toolset.py +3 -1
  298. agentpool-2.1.9.dist-info/RECORD +0 -474
  299. agentpool_config/resources.py +0 -33
  300. agentpool_server/acp_server/acp_tools.py +0 -43
  301. agentpool_server/acp_server/commands/spawn.py +0 -210
  302. agentpool_storage/text_log_provider.py +0 -275
  303. agentpool_toolsets/builtin/agent_management.py +0 -239
  304. agentpool_toolsets/builtin/chain.py +0 -288
  305. agentpool_toolsets/builtin/history.py +0 -36
  306. agentpool_toolsets/builtin/integration.py +0 -85
  307. agentpool_toolsets/builtin/tool_management.py +0 -90
  308. agentpool_toolsets/builtin/user_interaction.py +0 -52
  309. agentpool_toolsets/semantic_memory_toolset.py +0 -536
  310. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
  311. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,67 @@
1
+ """Mode definitions for agent behavior configuration.
2
+
3
+ Modes represent switchable behaviors/configurations that agents can expose
4
+ to clients. Each agent type can define its own mode categories.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from typing import Literal
11
+
12
+
13
+ @dataclass
14
+ class ModeInfo:
15
+ """Information about a single mode option.
16
+
17
+ Represents one selectable option within a mode category.
18
+ """
19
+
20
+ id: str
21
+ """Unique identifier for this mode."""
22
+
23
+ name: str
24
+ """Human-readable display name."""
25
+
26
+ description: str = ""
27
+ """Optional description of what this mode does."""
28
+
29
+ category_id: str = ""
30
+ """ID of the category this mode belongs to."""
31
+
32
+
33
+ @dataclass
34
+ class ModeCategory:
35
+ """A category of modes that can be switched.
36
+
37
+ Represents a group of mutually exclusive modes. In the future,
38
+ ACP may support multiple categories rendered as separate dropdowns.
39
+
40
+ Examples:
41
+ - Permissions: default, acceptEdits
42
+ - Behavior: plan, code, architect
43
+ """
44
+
45
+ id: str
46
+ """Unique identifier for this category."""
47
+
48
+ name: str
49
+ """Human-readable display name for the category."""
50
+
51
+ available_modes: list[ModeInfo] = field(default_factory=list)
52
+ """List of available modes in this category."""
53
+
54
+ current_mode_id: str = ""
55
+ """ID of the currently active mode."""
56
+
57
+ category: Literal["mode", "model", "thought_level", "other"] | None = None
58
+ """Optional semantic category for UX purposes (keyboard shortcuts, icons, placement).
59
+
60
+ This helps clients distinguish common selector types:
61
+ - 'mode': Session mode selector
62
+ - 'model': Model selector
63
+ - 'thought_level': Thought/reasoning level selector
64
+ - 'other': Unknown/uncategorized
65
+
66
+ MUST NOT be required for correctness. Clients should handle gracefully.
67
+ """
@@ -7,10 +7,6 @@ import re
7
7
  from typing import TYPE_CHECKING, Any, cast
8
8
 
9
9
  import anyio
10
- from slashed.events import (
11
- CommandExecutedEvent,
12
- CommandOutputEvent as SlashedCommandOutputEvent,
13
- )
14
10
 
15
11
  from agentpool.agents.events import CommandCompleteEvent, CommandOutputEvent
16
12
  from agentpool.log import get_logger
@@ -112,6 +108,11 @@ class SlashedAgent[TDeps, OutputDataT]:
112
108
  Yields:
113
109
  Command output and completion events
114
110
  """
111
+ from slashed.events import (
112
+ CommandExecutedEvent,
113
+ CommandOutputEvent as SlashedCommandOutputEvent,
114
+ )
115
+
115
116
  parsed = _parse_slash_command(command_text)
116
117
  if not parsed:
117
118
  logger.warning("Invalid slash command", command=command_text)
@@ -0,0 +1,213 @@
1
+ """Tool call accumulator for streaming tool arguments.
2
+
3
+ This module provides utilities for accumulating streamed tool call arguments
4
+ from LLM APIs that stream JSON arguments incrementally (like Anthropic's
5
+ input_json_delta or OpenAI's function call streaming).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ import anyenv
13
+
14
+
15
+ def repair_partial_json(buffer: str) -> str:
16
+ """Attempt to repair truncated JSON for preview purposes.
17
+
18
+ Handles common truncation cases:
19
+ - Unclosed strings
20
+ - Missing closing braces/brackets
21
+ - Trailing commas
22
+
23
+ Args:
24
+ buffer: Potentially incomplete JSON string
25
+
26
+ Returns:
27
+ Repaired JSON string (may still be invalid in edge cases)
28
+ """
29
+ if not buffer:
30
+ return "{}"
31
+
32
+ result = buffer.rstrip()
33
+
34
+ # Check if we're in the middle of a string by counting unescaped quotes
35
+ in_string = False
36
+ i = 0
37
+ while i < len(result):
38
+ char = result[i]
39
+ if char == "\\" and i + 1 < len(result):
40
+ i += 2 # Skip escaped character
41
+ continue
42
+ if char == '"':
43
+ in_string = not in_string
44
+ i += 1
45
+
46
+ # Close unclosed string
47
+ if in_string:
48
+ result += '"'
49
+
50
+ # Remove trailing comma (invalid JSON)
51
+ result = result.rstrip()
52
+ if result.endswith(","):
53
+ result = result[:-1]
54
+
55
+ # Balance braces and brackets
56
+ open_braces = result.count("{") - result.count("}")
57
+ open_brackets = result.count("[") - result.count("]")
58
+
59
+ result += "]" * max(0, open_brackets)
60
+ result += "}" * max(0, open_braces)
61
+
62
+ return result
63
+
64
+
65
+ class ToolCallAccumulator:
66
+ """Accumulates streamed tool call arguments.
67
+
68
+ LLM APIs stream tool call arguments as deltas. This class accumulates them
69
+ and provides the complete arguments when the tool call ends, as well as
70
+ best-effort partial argument parsing during streaming.
71
+
72
+ Example:
73
+ ```python
74
+ accumulator = ToolCallAccumulator()
75
+
76
+ # On content_block_start with tool_use
77
+ accumulator.start("toolu_123", "write_file")
78
+
79
+ # On input_json_delta events
80
+ accumulator.add_args("toolu_123", '{"path": "/tmp/')
81
+ accumulator.add_args("toolu_123", 'test.txt", "content"')
82
+ accumulator.add_args("toolu_123", ': "hello"}')
83
+
84
+ # Get partial args for UI preview (repairs incomplete JSON)
85
+ partial = accumulator.get_partial_args("toolu_123")
86
+
87
+ # On content_block_stop, get final parsed args
88
+ name, args = accumulator.complete("toolu_123")
89
+ ```
90
+ """
91
+
92
+ def __init__(self) -> None:
93
+ self._calls: dict[str, dict[str, Any]] = {}
94
+
95
+ def start(self, tool_call_id: str, tool_name: str) -> None:
96
+ """Start tracking a new tool call.
97
+
98
+ Args:
99
+ tool_call_id: Unique identifier for the tool call
100
+ tool_name: Name of the tool being called
101
+ """
102
+ self._calls[tool_call_id] = {"name": tool_name, "args_buffer": ""}
103
+
104
+ def add_args(self, tool_call_id: str, delta: str) -> None:
105
+ """Add argument delta to a tool call.
106
+
107
+ Args:
108
+ tool_call_id: Tool call identifier
109
+ delta: JSON string fragment to append
110
+ """
111
+ if tool_call_id in self._calls:
112
+ self._calls[tool_call_id]["args_buffer"] += delta
113
+
114
+ def complete(self, tool_call_id: str) -> tuple[str, dict[str, Any]] | None:
115
+ """Complete a tool call and return (tool_name, parsed_args).
116
+
117
+ Removes the tool call from tracking and returns the final parsed arguments.
118
+
119
+ Args:
120
+ tool_call_id: Tool call identifier
121
+
122
+ Returns:
123
+ Tuple of (tool_name, args_dict) or None if call not found
124
+ """
125
+ if tool_call_id not in self._calls:
126
+ return None
127
+
128
+ call_data = self._calls.pop(tool_call_id)
129
+ args_str = call_data["args_buffer"]
130
+ try:
131
+ args = anyenv.load_json(args_str) if args_str else {}
132
+ except anyenv.JsonLoadError:
133
+ args = {"_raw": args_str}
134
+ return call_data["name"], args
135
+
136
+ def get_pending(self, tool_call_id: str) -> tuple[str, str] | None:
137
+ """Get pending call data (tool_name, args_buffer) without completing.
138
+
139
+ Args:
140
+ tool_call_id: Tool call identifier
141
+
142
+ Returns:
143
+ Tuple of (tool_name, args_buffer) or None if not found
144
+ """
145
+ if tool_call_id not in self._calls:
146
+ return None
147
+ data = self._calls[tool_call_id]
148
+ return data["name"], data["args_buffer"]
149
+
150
+ def get_partial_args(self, tool_call_id: str) -> dict[str, Any]:
151
+ """Get best-effort parsed args from incomplete JSON stream.
152
+
153
+ Uses heuristics to complete truncated JSON for preview purposes.
154
+ Handles unclosed strings, missing braces/brackets, and trailing commas.
155
+
156
+ Args:
157
+ tool_call_id: Tool call ID
158
+
159
+ Returns:
160
+ Partially parsed arguments or empty dict
161
+ """
162
+ if tool_call_id not in self._calls:
163
+ return {}
164
+
165
+ buffer = self._calls[tool_call_id]["args_buffer"]
166
+ if not buffer:
167
+ return {}
168
+
169
+ # Try direct parse first
170
+ try:
171
+ result: dict[str, Any] = anyenv.load_json(buffer)
172
+ except anyenv.JsonLoadError:
173
+ pass
174
+ else:
175
+ return result
176
+
177
+ # Try to repair truncated JSON
178
+ try:
179
+ repaired = repair_partial_json(buffer)
180
+ result = anyenv.load_json(repaired)
181
+ except anyenv.JsonLoadError:
182
+ return {}
183
+ else:
184
+ return result
185
+
186
+ def is_pending(self, tool_call_id: str) -> bool:
187
+ """Check if a tool call is being tracked.
188
+
189
+ Args:
190
+ tool_call_id: Tool call identifier
191
+
192
+ Returns:
193
+ True if the tool call is being accumulated
194
+ """
195
+ return tool_call_id in self._calls
196
+
197
+ def get_tool_name(self, tool_call_id: str) -> str | None:
198
+ """Get the tool name for a pending call.
199
+
200
+ Args:
201
+ tool_call_id: Tool call identifier
202
+
203
+ Returns:
204
+ Tool name or None if not found
205
+ """
206
+ if tool_call_id not in self._calls:
207
+ return None
208
+ name: str = self._calls[tool_call_id]["name"]
209
+ return name
210
+
211
+ def clear(self) -> None:
212
+ """Clear all pending tool calls."""
213
+ self._calls.clear()
@@ -9,9 +9,11 @@ import time
9
9
  from typing import TYPE_CHECKING, Any
10
10
 
11
11
  from pydantic_ai import RunContext
12
+ from pydantic_ai.messages import ToolReturn
12
13
 
13
14
  from agentpool.agents.context import AgentContext
14
15
  from agentpool.tasks import ChainAbortedError, RunAbortedError, ToolSkippedError
16
+ from agentpool.tools.base import ToolResult
15
17
  from agentpool.utils.inspection import execute, get_argument_key
16
18
  from agentpool.utils.signatures import create_modified_signature, update_signature
17
19
 
@@ -27,7 +29,7 @@ def wrap_tool[TReturn]( # noqa: PLR0915
27
29
  tool: Tool[TReturn],
28
30
  agent_ctx: AgentContext,
29
31
  hooks: AgentHooks | None = None,
30
- ) -> Callable[..., Awaitable[TReturn | None]]:
32
+ ) -> Callable[..., Awaitable[TReturn | ToolReturn | None]]:
31
33
  """Wrap tool with confirmation handling and hooks.
32
34
 
33
35
  Strategy:
@@ -41,7 +43,7 @@ def wrap_tool[TReturn]( # noqa: PLR0915
41
43
  agent_ctx: Agent context for confirmation handling and dependency injection.
42
44
  hooks: Optional AgentHooks for pre/post tool execution hooks.
43
45
  """
44
- fn = tool.callable
46
+ fn = tool.get_callable()
45
47
  run_ctx_key = get_argument_key(fn, RunContext)
46
48
  agent_ctx_key = get_argument_key(fn, AgentContext)
47
49
 
@@ -58,7 +60,7 @@ def wrap_tool[TReturn]( # noqa: PLR0915
58
60
  tool_input: dict[str, Any],
59
61
  *args: Any,
60
62
  **kwargs: Any,
61
- ) -> TReturn | None:
63
+ ) -> TReturn | None | ToolReturn:
62
64
  """Execute tool with pre/post hooks."""
63
65
  # Pre-tool hooks
64
66
  if hooks:
@@ -79,9 +81,17 @@ def wrap_tool[TReturn]( # noqa: PLR0915
79
81
 
80
82
  # Execute the tool
81
83
  start_time = time.perf_counter()
82
- result = await execute_fn(*args, **kwargs)
84
+ result: TReturn | ToolResult | ToolReturn = await execute_fn(*args, **kwargs)
83
85
  duration_ms = (time.perf_counter() - start_time) * 1000
84
86
 
87
+ # Convert AgentPool ToolResult to pydantic-ai ToolReturn
88
+ if isinstance(result, ToolResult):
89
+ result = ToolReturn(
90
+ return_value=result.structured_content or result.content,
91
+ content=result.content,
92
+ metadata=result.metadata,
93
+ )
94
+
85
95
  # Post-tool hooks
86
96
  if hooks:
87
97
  await hooks.run_post_tool_hooks(
@@ -97,7 +107,9 @@ def wrap_tool[TReturn]( # noqa: PLR0915
97
107
 
98
108
  if run_ctx_key or agent_ctx_key:
99
109
  # Tool has RunContext and/or AgentContext
100
- async def wrapped(ctx: RunContext, *args: Any, **kwargs: Any) -> TReturn | None: # pyright: ignore
110
+ async def wrapped(
111
+ ctx: RunContext, *args: Any, **kwargs: Any
112
+ ) -> TReturn | None | ToolReturn: # pyright: ignore
101
113
  result = await agent_ctx.handle_confirmation(tool, kwargs)
102
114
  if result == "allow":
103
115
  # Populate AgentContext with RunContext data if needed
@@ -135,7 +147,7 @@ def wrap_tool[TReturn]( # noqa: PLR0915
135
147
 
136
148
  else:
137
149
  # Tool has no context - normal function call
138
- async def wrapped(*args: Any, **kwargs: Any) -> TReturn | None: # type: ignore[misc]
150
+ async def wrapped(*args: Any, **kwargs: Any) -> TReturn | None | ToolReturn: # type: ignore[misc]
139
151
  result = await agent_ctx.handle_confirmation(tool, kwargs)
140
152
  if result == "allow":
141
153
  tool_input = kwargs.copy()
agentpool/common_types.py CHANGED
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from collections.abc import Awaitable, Callable
6
+ from dataclasses import dataclass
6
7
  from typing import (
7
8
  TYPE_CHECKING,
8
9
  Any,
@@ -27,15 +28,13 @@ if TYPE_CHECKING:
27
28
  from uuid import UUID
28
29
 
29
30
  from agentpool.agents.events import RichAgentStreamEvent
30
- from agentpool.messaging import ChatMessage
31
- from agentpool.messaging.messagenode import MessageNode
32
31
  from agentpool.tools.base import Tool
33
32
 
34
- # Type alias for team streaming return type (node + event tuples)
35
- type TeamStreamEvent = tuple[MessageNode[Any, Any], RichAgentStreamEvent[Any]]
36
33
  type AnyTransformFn[T] = Callable[[T], T | Awaitable[T]]
37
34
  type OptionalAwaitable[T] = T | Awaitable[T]
38
- type ToolType = str | AnyCallable | Tool
35
+ # Import path string for dynamic tool loading (e.g., "mymodule:my_tool")
36
+ type ImportPathString = str
37
+ type ToolType = ImportPathString | AnyCallable | Tool
39
38
  # Define what we consider JSON-serializable
40
39
  type JsonPrimitive = None | bool | int | float | str
41
40
  type SessionIdType = str | UUID | None
@@ -50,12 +49,32 @@ type JsonObject = dict[str, JsonValue]
50
49
  type JsonArray = list[JsonValue]
51
50
 
52
51
 
52
+ MCPConnectionStatus = Literal["connected", "disconnected", "error"]
53
+
54
+
55
+ @dataclass(frozen=True, slots=True)
56
+ class MCPServerStatus:
57
+ """Status information for an MCP server.
58
+
59
+ Attributes:
60
+ name: Server name/identifier
61
+ status: Connection status
62
+ server_type: Transport type (stdio, sse, http)
63
+ error: Error message if status is "error"
64
+ """
65
+
66
+ name: str
67
+ status: MCPConnectionStatus
68
+ server_type: str = "unknown"
69
+ error: str | None = None
70
+
71
+
53
72
  NodeName = str
54
73
  TeamName = str
55
74
  AgentName = str
56
75
  MessageRole = Literal["user", "assistant"]
57
76
  PartType = Literal["text", "image", "audio", "video"]
58
- ModelType = Model | ModelId | str | None
77
+ ModelType = Model | ModelId | str
59
78
  EnvironmentType = Literal["file", "inline"]
60
79
  ToolSource = Literal["agent", "builtin", "dynamic", "task", "mcp", "toolset"]
61
80
  AnyCallable = Callable[..., Any]
@@ -78,30 +97,46 @@ QueueStrategy = Literal["concat", "latest", "buffer"]
78
97
  """
79
98
 
80
99
 
81
- class SupportsStructuredOutput(Protocol):
82
- """Protocol for nodes that support structured output via run().
83
-
84
- This protocol is used for components that need to call run() with
85
- an output_type parameter (e.g., picker agents, Interactions).
86
- """
87
-
88
- async def run(self, *prompts: Any, output_type: Any = ...) -> ChatMessage[Any]: ...
89
-
90
-
91
100
  @runtime_checkable
92
101
  class SupportsRunStream[TResult](Protocol):
93
102
  """Protocol for nodes that support streaming via run_stream().
94
103
 
95
104
  Used by Team and TeamRun to check if a node can be streamed.
96
-
97
- Return type is a union because:
98
- - Agent returns `AsyncIterator[RichAgentStreamEvent[TResult]]`
99
- - Team/TeamRun return `AsyncIterator[tuple[MessageNode, RichAgentStreamEvent]]`
105
+ All streaming nodes return RichAgentStreamEvent, with subagent/team
106
+ activity wrapped in SubAgentEvent.
100
107
  """
101
108
 
102
109
  def run_stream(
103
110
  self, *prompts: Any, **kwargs: Any
104
- ) -> AsyncIterator[RichAgentStreamEvent[TResult] | TeamStreamEvent]: ...
111
+ ) -> AsyncIterator[RichAgentStreamEvent[TResult]]: ...
112
+
113
+
114
+ @runtime_checkable
115
+ class SupportsStructuredOutput(Protocol):
116
+ """Protocol for agents that support structured output via to_structured().
117
+
118
+ Used by Interactions class for pick/extract operations that require
119
+ structured output from agents.
120
+ """
121
+
122
+ def to_structured[T](self, output_type: type[T]) -> SupportsStructuredOutput:
123
+ """Create a copy of this agent configured for structured output.
124
+
125
+ Args:
126
+ output_type: The type to structure output as
127
+
128
+ Returns:
129
+ New agent instance configured for structured output
130
+ """
131
+ ...
132
+
133
+ async def run(self, *prompts: Any, **kwargs: Any) -> Any:
134
+ """Run the agent with the given prompts.
135
+
136
+ Returns:
137
+ ChatMessage with content typed according to output_type from to_structured
138
+ """
139
+ ...
105
140
 
106
141
 
107
142
  class BaseCode(BaseModel):
@@ -7,10 +7,47 @@ from typing import Final
7
7
 
8
8
  _RESOURCES = importlib.resources.files("agentpool.config_resources")
9
9
 
10
+ # Template configuration
10
11
  AGENTS_TEMPLATE: Final[str] = str(_RESOURCES / "agents_template.yml")
11
12
  """Path to the agents template configuration."""
12
13
 
14
+ # Pool configurations
13
15
  ACP_ASSISTANT: Final[str] = str(_RESOURCES / "acp_assistant.yml")
14
16
  """Path to default ACP assistant configuration."""
15
17
 
16
- __all__ = ["ACP_ASSISTANT", "AGENTS_TEMPLATE"]
18
+ AGENTS: Final[str] = str(_RESOURCES / "agents.yml")
19
+ """Path to the main agents configuration."""
20
+
21
+ AGUI_TEST: Final[str] = str(_RESOURCES / "agui_test.yml")
22
+ """Path to AGUI test configuration."""
23
+
24
+ CLAUDE_CODE_ASSISTANT: Final[str] = str(_RESOURCES / "claude_code_agent.yml")
25
+ """Path to default Claude code assistant configuration."""
26
+
27
+ EXTERNAL_ACP_AGENTS: Final[str] = str(_RESOURCES / "external_acp_agents.yml")
28
+ """Path to external ACP agents configuration."""
29
+
30
+ TTS_TEST_AGENTS: Final[str] = str(_RESOURCES / "tts_test_agents.yml")
31
+ """Path to TTS test agents configuration."""
32
+
33
+ # All pool configuration paths for validation
34
+ ALL_POOL_CONFIGS: Final[tuple[str, ...]] = (
35
+ ACP_ASSISTANT,
36
+ AGENTS,
37
+ AGUI_TEST,
38
+ CLAUDE_CODE_ASSISTANT,
39
+ EXTERNAL_ACP_AGENTS,
40
+ TTS_TEST_AGENTS,
41
+ )
42
+ """All pool configuration file paths."""
43
+
44
+ __all__ = [
45
+ "ACP_ASSISTANT",
46
+ "AGENTS",
47
+ "AGENTS_TEMPLATE",
48
+ "AGUI_TEST",
49
+ "ALL_POOL_CONFIGS",
50
+ "CLAUDE_CODE_ASSISTANT",
51
+ "EXTERNAL_ACP_AGENTS",
52
+ "TTS_TEST_AGENTS",
53
+ ]
@@ -17,8 +17,8 @@ agents:
17
17
  You are a helpful AI assistant with access to the file system and can execute code.
18
18
  You can help with coding, writing, analysis, file operations, and more.
19
19
  Be concise but thorough in your responses.
20
- toolsets:
21
- - type: execution
20
+ tools:
21
+ - type: process_management
22
22
  - type: file_access
23
23
  - type: code
24
24
  - type: search
@@ -47,6 +47,7 @@ responses:
47
47
 
48
48
  agents:
49
49
  url_opener:
50
+ type: native
50
51
  tools:
51
52
  - type: import
52
53
  import_path: "webbrowser.open"
@@ -63,6 +64,7 @@ agents:
63
64
  Always confirm what you're about to open.
64
65
 
65
66
  system_inspector:
67
+ type: native
66
68
  tools:
67
69
  - type: import
68
70
  import_path: "platform.platform"
@@ -84,6 +86,7 @@ agents:
84
86
  Format it using the template from the system_template resource.
85
87
 
86
88
  file_explorer:
89
+ type: native
87
90
  tools:
88
91
  - type: import
89
92
  import_path: "os.listdir"
@@ -1,6 +1,7 @@
1
1
  # yaml-language-server: $schema=https://raw.githubusercontent.com/phil65/agentpool/refs/heads/main/schema/config-schema.json
2
2
  agents:
3
3
  simple_agent:
4
+ type: native
4
5
  description: "Basic agent with one tool to open webbrowsers."
5
6
  model: openai:gpt-5-mini
6
7
  tools:
@@ -1,16 +1,20 @@
1
1
  # yaml-language-server: $schema=https://raw.githubusercontent.com/phil65/agentpool/refs/heads/main/schema/config-schema.json
2
- # Default ACP assistant configuration
3
- # Used when starting ACP server without explicit config file
4
2
 
5
3
  agents:
6
4
  claude:
7
5
  type: claude_code
8
6
  display_name: "AI Assistant"
9
- builtin_tools: []
7
+ builtin_tools: [] # we use our own tools.
8
+ use_subscription: true
10
9
  description: "Claude code agent with enhanced tools"
11
- toolsets:
12
- - type: execution
10
+ tools:
11
+ - type: bash
12
+ - type: agent_cli
13
+ - type: process_management
13
14
  - type: file_access
14
- - type: code
15
+ edit_tool: batch
16
+ # - type: code
15
17
  - type: search
16
18
  - type: plan
19
+ - type: question
20
+ # - type: debug
@@ -9,9 +9,10 @@ agents:
9
9
  env: # override to make sure subscription is used
10
10
  ANTHROPIC_API_KEY: ""
11
11
 
12
- toolsets:
12
+ tools:
13
13
  - type: code
14
14
  - type: subagent
15
+ - type: plan
15
16
  codex:
16
17
  type: acp
17
18
  provider: codex