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
acp/__init__.py CHANGED
@@ -3,7 +3,6 @@
3
3
  from acp.client import DefaultACPClient, HeadlessACPClient, NoOpClient, ClientSideConnection
4
4
  from acp.agent import AgentSideConnection
5
5
  from acp.bridge import ACPBridge, BridgeSettings
6
- from acp.filesystem import ACPFileSystem, ACPPath
7
6
  from acp.agent.protocol import Agent
8
7
  from acp.client.protocol import Client
9
8
  from acp.terminal_handle import TerminalHandle
@@ -73,6 +72,13 @@ from acp.schema import (
73
72
  ToolCall,
74
73
  )
75
74
  from acp.stdio import stdio_streams, run_agent, connect_to_agent
75
+ from acp.transports import (
76
+ serve,
77
+ StdioTransport,
78
+ WebSocketTransport,
79
+ StreamTransport,
80
+ Transport,
81
+ )
76
82
  from acp.exceptions import RequestError
77
83
 
78
84
  __version__ = "0.0.1"
@@ -166,7 +172,10 @@ __all__ = [ # noqa: RUF022
166
172
  "FileSystemCapability",
167
173
  # stdio helper
168
174
  "stdio_streams",
169
- # filesystem
170
- "ACPFileSystem",
171
- "ACPPath",
175
+ # transport
176
+ "serve",
177
+ "StdioTransport",
178
+ "WebSocketTransport",
179
+ "StreamTransport",
180
+ "Transport",
172
181
  ]
acp/acp_requests.py CHANGED
@@ -3,7 +3,9 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
- from typing import TYPE_CHECKING
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ import structlog
7
9
 
8
10
  from acp.schema import (
9
11
  CreateTerminalRequest,
@@ -19,7 +21,6 @@ from acp.schema import (
19
21
  WriteTextFileRequest,
20
22
  )
21
23
  from acp.terminal_handle import TerminalHandle
22
- from agentpool.log import get_logger
23
24
 
24
25
 
25
26
  if TYPE_CHECKING:
@@ -30,20 +31,10 @@ if TYPE_CHECKING:
30
31
  WaitForTerminalExitResponse,
31
32
  )
32
33
 
33
- logger = get_logger(__name__)
34
+ logger = structlog.get_logger(__name__)
34
35
 
35
36
 
36
- RULES_FILE_NAMES = [
37
- ".rules",
38
- "CLAUDE.md",
39
- "AGENT.md",
40
- "AGENTS.md",
41
- "GEMINI.md",
42
- ".cursorrules",
43
- ".windsurfrules",
44
- ".clinerules",
45
- ".github/copilot-instructions.md",
46
- ]
37
+ RULES_FILE_NAMES = ["CLAUDE.md", "AGENTS.md"]
47
38
 
48
39
 
49
40
  class ACPRequests:
@@ -54,12 +45,7 @@ class ACPRequests:
54
45
  """
55
46
 
56
47
  def __init__(self, client: Client, session_id: str) -> None:
57
- """Initialize requests helper.
58
-
59
- Args:
60
- client: ACP client
61
- session_id: Session ID
62
- """
48
+ """Initialize requests helper."""
63
49
  self.client = client
64
50
  self.id = session_id
65
51
 
@@ -80,12 +66,7 @@ class ACPRequests:
80
66
  Returns:
81
67
  File content as string
82
68
  """
83
- request = ReadTextFileRequest(
84
- session_id=self.id,
85
- path=path,
86
- limit=limit,
87
- line=line,
88
- )
69
+ request = ReadTextFileRequest(session_id=self.id, path=path, limit=limit, line=line)
89
70
  response = await self.client.read_text_file(request)
90
71
  return response.content
91
72
 
@@ -104,12 +85,7 @@ class ACPRequests:
104
85
  return None
105
86
 
106
87
  async def write_text_file(self, path: str, content: str) -> None:
107
- """Write text content to a file.
108
-
109
- Args:
110
- path: File path to write
111
- content: Text content to write
112
- """
88
+ """Write text content to a file."""
113
89
  request = WriteTextFileRequest(session_id=self.id, path=path, content=content)
114
90
  await self.client.write_text_file(request)
115
91
 
@@ -146,47 +122,22 @@ class ACPRequests:
146
122
  return TerminalHandle(terminal_id=response.terminal_id, requests=self)
147
123
 
148
124
  async def terminal_output(self, terminal_id: str) -> TerminalOutputResponse:
149
- """Get output from a terminal session.
150
-
151
- Args:
152
- terminal_id: Terminal identifier
153
-
154
- Returns:
155
- Terminal output response
156
- """
125
+ """Get output from a terminal session."""
157
126
  request = TerminalOutputRequest(session_id=self.id, terminal_id=terminal_id)
158
127
  return await self.client.terminal_output(request)
159
128
 
160
- async def wait_for_terminal_exit(
161
- self,
162
- terminal_id: str,
163
- ) -> WaitForTerminalExitResponse:
164
- """Wait for a terminal to exit.
165
-
166
- Args:
167
- terminal_id: Terminal identifier
168
-
169
- Returns:
170
- Terminal exit response with exit_code
171
- """
129
+ async def wait_for_terminal_exit(self, terminal_id: str) -> WaitForTerminalExitResponse:
130
+ """Wait for a terminal to exit."""
172
131
  request = WaitForTerminalExitRequest(session_id=self.id, terminal_id=terminal_id)
173
132
  return await self.client.wait_for_terminal_exit(request)
174
133
 
175
134
  async def kill_terminal(self, terminal_id: str) -> None:
176
- """Kill a terminal session.
177
-
178
- Args:
179
- terminal_id: Terminal identifier to kill
180
- """
135
+ """Kill a terminal session."""
181
136
  request = KillTerminalCommandRequest(session_id=self.id, terminal_id=terminal_id)
182
137
  await self.client.kill_terminal(request)
183
138
 
184
139
  async def release_terminal(self, terminal_id: str) -> None:
185
- """Release a terminal session.
186
-
187
- Args:
188
- terminal_id: Terminal identifier to release
189
- """
140
+ """Release a terminal session."""
190
141
  request = ReleaseTerminalRequest(session_id=self.id, terminal_id=terminal_id)
191
142
  await self.client.release_terminal(request)
192
143
 
@@ -228,10 +179,8 @@ class ACPRequests:
228
179
  try:
229
180
  if timeout_seconds: # Wait for completion (with optional timeout)
230
181
  try:
231
- exit_result = await asyncio.wait_for(
232
- self.wait_for_terminal_exit(terminal_id),
233
- timeout=timeout_seconds,
234
- )
182
+ coro = self.wait_for_terminal_exit(terminal_id)
183
+ exit_result = await asyncio.wait_for(coro, timeout=timeout_seconds)
235
184
  except TimeoutError: # Kill on timeout and get partial output
236
185
  await self.kill_terminal(terminal_id)
237
186
  output_response = await self.terminal_output(terminal_id)
@@ -250,6 +199,7 @@ class ACPRequests:
250
199
  tool_call_id: str,
251
200
  *,
252
201
  title: str | None = None,
202
+ raw_input: Any | None = None,
253
203
  options: list[PermissionOption] | None = None,
254
204
  ) -> RequestPermissionResponse:
255
205
  """Request permission from user before executing a tool call.
@@ -257,6 +207,7 @@ class ACPRequests:
257
207
  Args:
258
208
  tool_call_id: Unique identifier for the tool call
259
209
  title: Human-readable description of the operation
210
+ raw_input: The raw input parameters for the tool call
260
211
  options: Available permission options (defaults to allow/reject once)
261
212
 
262
213
  Returns:
@@ -264,19 +215,11 @@ class ACPRequests:
264
215
  """
265
216
  if options is None:
266
217
  options = [
267
- PermissionOption(
268
- option_id="allow-once",
269
- name="Allow once",
270
- kind="allow_once",
271
- ),
272
- PermissionOption(
273
- option_id="reject-once",
274
- name="Reject",
275
- kind="reject_once",
276
- ),
218
+ PermissionOption(option_id="allow-once", name="Allow once", kind="allow_once"),
219
+ PermissionOption(option_id="reject-once", name="Reject", kind="reject_once"),
277
220
  ]
278
221
 
279
- tool_call = ToolCall(tool_call_id=tool_call_id, title=title)
222
+ tool_call = ToolCall(tool_call_id=tool_call_id, title=title, raw_input=raw_input)
280
223
  request = RequestPermissionRequest(
281
224
  session_id=self.id,
282
225
  tool_call=tool_call,
acp/agent/connection.py CHANGED
@@ -30,6 +30,7 @@ from acp.schema import (
30
30
  RequestPermissionRequest,
31
31
  RequestPermissionResponse,
32
32
  SessionNotification,
33
+ SetSessionConfigOptionRequest,
33
34
  SetSessionModelRequest,
34
35
  SetSessionModeRequest,
35
36
  TerminalOutputRequest,
@@ -243,6 +244,13 @@ async def _agent_handler( # noqa: PLR0911
243
244
  if (model_result := await agent.set_session_model(set_model_request))
244
245
  else {}
245
246
  )
247
+ case "session/set_config_option":
248
+ set_config_request = SetSessionConfigOptionRequest.model_validate(params)
249
+ return (
250
+ config_result.model_dump(by_alias=True, exclude_none=True)
251
+ if (config_result := await agent.set_session_config_option(set_config_request))
252
+ else {}
253
+ )
246
254
  case "authenticate":
247
255
  p = AuthenticateRequest.model_validate(params)
248
256
  result = await agent.authenticate(p)
@@ -25,6 +25,7 @@ import anyio
25
25
  from fastapi import FastAPI, HTTPException
26
26
  from fastapi.responses import HTMLResponse
27
27
  from pydantic import BaseModel, Field
28
+ import structlog
28
29
  import uvicorn
29
30
 
30
31
  from acp import AgentSideConnection
@@ -57,7 +58,6 @@ from acp.schema import (
57
58
  WriteTextFileResponse,
58
59
  )
59
60
  from acp.stdio import stdio_streams
60
- from agentpool.log import get_logger
61
61
 
62
62
 
63
63
  if TYPE_CHECKING:
@@ -81,7 +81,7 @@ if TYPE_CHECKING:
81
81
  )
82
82
 
83
83
 
84
- logger = get_logger(__name__)
84
+ logger = structlog.get_logger(__name__)
85
85
  MOCK_FILE = """\
86
86
  # Mock file: {path}
87
87
  # Generated by ACP Debug Server
@@ -190,6 +190,10 @@ class MockAgent(Agent):
190
190
  """Mock session model change."""
191
191
  logger.info("Mock session model change")
192
192
 
193
+ async def set_session_config_option(self, params: Any) -> None:
194
+ """Mock session config option change."""
195
+ logger.info("Mock session config option change")
196
+
193
197
  async def ext_notification(self, method: str, params: dict[str, Any]) -> None:
194
198
  """Mock extensibility notification."""
195
199
  logger.info("Mock ext notification", method=method)
acp/agent/protocol.py CHANGED
@@ -24,6 +24,8 @@ if TYPE_CHECKING:
24
24
  PromptResponse,
25
25
  ResumeSessionRequest,
26
26
  ResumeSessionResponse,
27
+ SetSessionConfigOptionRequest,
28
+ SetSessionConfigOptionResponse,
27
29
  SetSessionModelRequest,
28
30
  SetSessionModelResponse,
29
31
  SetSessionModeRequest,
@@ -60,6 +62,10 @@ class Agent(Protocol):
60
62
  self, params: SetSessionModelRequest
61
63
  ) -> SetSessionModelResponse | None: ...
62
64
 
65
+ async def set_session_config_option(
66
+ self, params: SetSessionConfigOptionRequest
67
+ ) -> SetSessionConfigOptionResponse | None: ...
68
+
63
69
  async def ext_method(self, method: str, params: dict[str, Any]) -> dict[str, Any]: ...
64
70
 
65
71
  async def ext_notification(self, method: str, params: dict[str, Any]) -> None: ...
acp/bridge/README.md CHANGED
@@ -1,10 +1,23 @@
1
1
  # ACP Bridge
2
2
 
3
- A stdio-to-HTTP bridge for ACP (Agent Client Protocol) agents.
3
+ Bridges for exposing **external** stdio-based ACP agents over network transports.
4
+
5
+ ## When to Use
6
+
7
+ **Use the bridge** when you need to expose an **external** stdio-based ACP agent (one you don't control) over HTTP or WebSocket.
8
+
9
+ **Use native transports** when building your own agent with the `acp` library. The `acp.serve()` function supports multiple transports directly:
10
+
11
+ ```python
12
+ from acp import serve, WebSocketTransport
13
+
14
+ # Native WebSocket transport (no bridge needed)
15
+ await serve(my_agent, WebSocketTransport(host="0.0.0.0", port=8765))
16
+ ```
4
17
 
5
18
  ## Overview
6
19
 
7
- The ACP Bridge allows you to expose stdio-based ACP agents via a streamable HTTP endpoint. This is useful when you want to run an agent as a subprocess but communicate with it over HTTP instead of stdio.
20
+ The ACP Bridge allows you to expose stdio-based ACP agents via HTTP or WebSocket endpoints. This is useful when you want to run an external agent as a subprocess but communicate with it over the network instead of stdio.
8
21
 
9
22
  ## Features
10
23
 
acp/bridge/__init__.py CHANGED
@@ -1,6 +1,7 @@
1
- """ACP Bridge - stdio to streamable-http transport bridge for ACP agents."""
1
+ """ACP Bridge - transport bridges for ACP agents."""
2
2
 
3
3
  from acp.bridge.bridge import ACPBridge
4
4
  from acp.bridge.settings import BridgeSettings
5
+ from acp.bridge.ws_server import ACPWebSocketServer
5
6
 
6
- __all__ = ["ACPBridge", "BridgeSettings"]
7
+ __all__ = ["ACPBridge", "ACPWebSocketServer", "BridgeSettings"]
acp/bridge/__main__.py CHANGED
@@ -1,8 +1,13 @@
1
1
  """CLI entry point for running the ACP bridge.
2
2
 
3
3
  Usage:
4
- uv run -m acp_bridge <command> [args...]
5
- uv run -m acp_bridge --port 8080 -- your-agent-command --arg1 value1
4
+ # HTTP bridge (default)
5
+ uv run -m acp.bridge <command> [args...]
6
+ uv run -m acp.bridge --port 8080 -- your-agent-command --arg1 value1
7
+
8
+ # WebSocket bridge
9
+ uv run -m acp.bridge --transport websocket <command> [args...]
10
+ uv run -m acp.bridge -t ws --port 8765 -- uv run agentpool serve-acp
6
11
  """
7
12
 
8
13
  from __future__ import annotations
@@ -18,24 +23,35 @@ import anyio
18
23
  def main() -> None:
19
24
  """Run the ACP bridge from command line."""
20
25
  parser = argparse.ArgumentParser(
21
- description="Bridge a stdio ACP agent to streamable HTTP transport.",
26
+ description="Bridge a stdio ACP agent to HTTP or WebSocket transport.",
22
27
  epilog=(
23
28
  "Examples:\n"
29
+ " # HTTP bridge\n"
24
30
  " acp-bridge your-agent-command\n"
25
31
  " acp-bridge --port 8080 -- your-agent --config config.yml\n"
26
- " acp-bridge -H 0.0.0.0 -p 9000 -- uv run my-agent\n"
32
+ "\n"
33
+ " # WebSocket bridge\n"
34
+ " acp-bridge -t ws -- uv run agentpool serve-acp\n"
35
+ " acp-bridge --transport websocket --port 8765 -- uv run my-agent\n"
27
36
  ),
28
37
  formatter_class=argparse.RawTextHelpFormatter,
29
38
  )
30
39
 
31
40
  parser.add_argument("command", help="Command to spawn the ACP agent.")
32
41
  parser.add_argument("args", nargs="*", help="Arguments for the agent command.")
42
+ parser.add_argument(
43
+ "-t",
44
+ "--transport",
45
+ choices=["http", "websocket", "ws"],
46
+ default="http",
47
+ help="Transport type: http (default) or websocket/ws",
48
+ )
33
49
  parser.add_argument(
34
50
  "-p",
35
51
  "--port",
36
52
  type=int,
37
- default=8080,
38
- help="Port to serve the HTTP endpoint on. Default: 8080",
53
+ default=None,
54
+ help="Port to serve on. Default: 8080 (HTTP) or 8765 (WebSocket)",
39
55
  )
40
56
  parser.add_argument(
41
57
  "-H",
@@ -53,7 +69,7 @@ def main() -> None:
53
69
  "--allow-origin",
54
70
  action="append",
55
71
  default=[],
56
- help="Allowed CORS origins. Can be specified multiple times.",
72
+ help="Allowed CORS origins (HTTP only). Can be specified multiple times.",
57
73
  )
58
74
  parser.add_argument(
59
75
  "--cwd",
@@ -69,22 +85,47 @@ def main() -> None:
69
85
 
70
86
  logging.basicConfig(
71
87
  level=getattr(logging, parsed.log_level),
72
- format="[%(levelname)1.1s %(asctime)s.%(msecs)03d %(name)s] %(message)s",
73
- datefmt="%H:%M:%S",
88
+ format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
74
89
  )
75
90
 
76
- from acp.bridge import ACPBridge, BridgeSettings
91
+ use_websocket = parsed.transport in ("websocket", "ws")
77
92
 
78
- settings = BridgeSettings(
79
- host=parsed.host,
80
- port=parsed.port,
81
- log_level=parsed.log_level,
82
- allow_origins=parsed.allow_origin if parsed.allow_origin else None,
83
- )
93
+ if use_websocket:
94
+ # Reduce websockets noise
95
+ logging.getLogger("websockets").setLevel(logging.WARNING)
96
+
97
+ from acp.bridge.ws_server import ACPWebSocketServer
98
+
99
+ port = parsed.port or 8765
100
+ server = ACPWebSocketServer(
101
+ command=parsed.command,
102
+ args=parsed.args,
103
+ host=parsed.host,
104
+ port=port,
105
+ cwd=parsed.cwd,
106
+ )
107
+
108
+ print(f"🔗 ACP WebSocket bridge starting on ws://{parsed.host}:{port}")
109
+ print(f" Agent command: {parsed.command} {' '.join(parsed.args)}")
110
+
111
+ with contextlib.suppress(KeyboardInterrupt):
112
+ anyio.run(server.serve)
113
+ else:
114
+ from acp.bridge import ACPBridge, BridgeSettings
115
+
116
+ port = parsed.port or 8080
117
+ settings = BridgeSettings(
118
+ host=parsed.host,
119
+ port=port,
120
+ log_level=parsed.log_level,
121
+ allow_origins=parsed.allow_origin if parsed.allow_origin else None,
122
+ )
84
123
 
85
- bridge = ACPBridge(command=parsed.command, args=parsed.args, cwd=parsed.cwd, settings=settings)
86
- with contextlib.suppress(KeyboardInterrupt):
87
- anyio.run(bridge.run)
124
+ bridge = ACPBridge(
125
+ command=parsed.command, args=parsed.args, cwd=parsed.cwd, settings=settings
126
+ )
127
+ with contextlib.suppress(KeyboardInterrupt):
128
+ anyio.run(bridge.run)
88
129
 
89
130
 
90
131
  if __name__ == "__main__":
@@ -0,0 +1,173 @@
1
+ """WebSocket server that bridges to a stdio ACP agent.
2
+
3
+ Spawns a stdio-based ACP agent subprocess and exposes it via WebSocket,
4
+ allowing clients like mcp-ws to connect.
5
+
6
+ Usage:
7
+ python -m acp.bridge.ws_server "uv run agentpool serve-acp" --port 8765
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ import contextlib
14
+ import json
15
+ import logging
16
+ from typing import TYPE_CHECKING, Any
17
+
18
+ import anyenv
19
+ import websockets
20
+
21
+ from acp.transports import spawn_stdio_transport
22
+
23
+
24
+ if TYPE_CHECKING:
25
+ from collections.abc import Mapping
26
+ from pathlib import Path
27
+
28
+ from anyio.abc import ByteReceiveStream, ByteSendStream, Process
29
+ from websockets.asyncio.server import ServerConnection
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ class ACPWebSocketServer:
35
+ """WebSocket server that bridges to a stdio ACP agent."""
36
+
37
+ def __init__(
38
+ self,
39
+ command: str,
40
+ args: list[str] | None = None,
41
+ *,
42
+ host: str = "localhost",
43
+ port: int = 8765,
44
+ env: Mapping[str, str] | None = None,
45
+ cwd: str | Path | None = None,
46
+ ) -> None:
47
+ """Initialize the WebSocket server.
48
+
49
+ Args:
50
+ command: Command to spawn the ACP agent.
51
+ args: Arguments for the command.
52
+ host: Host to bind the WebSocket server to.
53
+ port: Port for the WebSocket server.
54
+ env: Environment variables for the subprocess.
55
+ cwd: Working directory for the subprocess.
56
+ """
57
+ self.command = command
58
+ self.args = args or []
59
+ self.host = host
60
+ self.port = port
61
+ self.env = env
62
+ self.cwd = cwd
63
+
64
+ self._process: Process | None = None
65
+ self._reader: ByteReceiveStream | None = None
66
+ self._writer: ByteSendStream | None = None
67
+ self._websocket: ServerConnection | None = None
68
+ self._shutdown = asyncio.Event()
69
+ self._read_buffer = b""
70
+
71
+ async def _read_jsonrpc_message(self) -> dict[str, Any] | None:
72
+ """Read a JSON-RPC message from the agent's stdout."""
73
+ if self._reader is None:
74
+ return None
75
+
76
+ while True:
77
+ # Check if we have a complete line in buffer
78
+ if b"\n" in self._read_buffer:
79
+ line, self._read_buffer = self._read_buffer.split(b"\n", 1)
80
+ if line.strip():
81
+ try:
82
+ return anyenv.load_json(line.decode(), return_type=dict)
83
+ except anyenv.JsonLoadError:
84
+ logger.exception("Failed to parse JSON from agent")
85
+ continue
86
+
87
+ # Read more data
88
+ try:
89
+ chunk = await self._reader.receive(65536)
90
+ if not chunk:
91
+ return None
92
+ self._read_buffer += chunk
93
+ except Exception: # noqa: BLE001
94
+ return None
95
+
96
+ async def _send_to_agent(self, message: dict[str, Any]) -> None:
97
+ """Send a JSON-RPC message to the agent's stdin."""
98
+ if self._writer is None:
99
+ return
100
+
101
+ data = json.dumps(message) + "\n"
102
+ await self._writer.send(data.encode())
103
+
104
+ async def _agent_to_websocket(self) -> None:
105
+ """Forward messages from agent stdout to WebSocket."""
106
+ while not self._shutdown.is_set():
107
+ try:
108
+ message = await self._read_jsonrpc_message()
109
+ if message is None:
110
+ logger.info("Agent stdout closed")
111
+ break
112
+
113
+ if self._websocket is not None:
114
+ await self._websocket.send(json.dumps(message))
115
+ logger.debug("Agent → WebSocket: %s", message.get("method", message.get("id")))
116
+ except Exception:
117
+ logger.exception("Error forwarding agent → WebSocket")
118
+ break
119
+
120
+ async def _handle_client(self, websocket: ServerConnection) -> None:
121
+ """Handle a WebSocket client connection."""
122
+ logger.info("WebSocket client connected")
123
+ self._websocket = websocket
124
+
125
+ try:
126
+ async for raw_message in websocket:
127
+ try:
128
+ message = json.loads(raw_message)
129
+ logger.debug("WebSocket → Agent: %s", message.get("method", message.get("id")))
130
+ await self._send_to_agent(message)
131
+ except json.JSONDecodeError:
132
+ logger.exception("Invalid JSON from WebSocket")
133
+ error_response = {
134
+ "jsonrpc": "2.0",
135
+ "id": None,
136
+ "error": {"code": -32700, "message": "Parse error"},
137
+ }
138
+ await websocket.send(json.dumps(error_response))
139
+ except websockets.exceptions.ConnectionClosed:
140
+ logger.info("WebSocket client disconnected")
141
+ finally:
142
+ self._websocket = None
143
+
144
+ async def serve(self) -> None:
145
+ """Start the WebSocket server and agent process."""
146
+ cmd_str = f"{self.command} {' '.join(self.args)}".strip()
147
+ logger.info("Starting ACP agent: %s", cmd_str)
148
+
149
+ async with spawn_stdio_transport(self.command, *self.args, env=self.env, cwd=self.cwd) as (
150
+ reader,
151
+ writer,
152
+ process,
153
+ ):
154
+ self._reader = reader
155
+ self._writer = writer
156
+ self._process = process
157
+
158
+ # Start forwarding agent output to WebSocket
159
+ forward_task = asyncio.create_task(self._agent_to_websocket())
160
+
161
+ try:
162
+ async with websockets.serve(
163
+ self._handle_client,
164
+ self.host,
165
+ self.port,
166
+ ):
167
+ logger.info("WebSocket server running on ws://%s:%d", self.host, self.port)
168
+ await self._shutdown.wait()
169
+ finally:
170
+ self._shutdown.set()
171
+ forward_task.cancel()
172
+ with contextlib.suppress(asyncio.CancelledError):
173
+ await forward_task