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,89 @@
1
+ """CLI entry point for the ACP WebSocket server bridge.
2
+
3
+ Usage:
4
+ python -m acp.bridge.ws_server_cli "uv run agentpool serve-acp"
5
+ python -m acp.bridge.ws_server_cli --port 8765 -- uv run agentpool serve-acp
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import contextlib
12
+ import logging
13
+ import sys
14
+
15
+ import anyio
16
+
17
+
18
+ def main() -> None:
19
+ """Run the ACP WebSocket server bridge from command line."""
20
+ parser = argparse.ArgumentParser(
21
+ description="Bridge a stdio ACP agent to WebSocket transport.",
22
+ epilog=(
23
+ "Examples:\n"
24
+ ' acp-ws-server "uv run agentpool serve-acp"\n'
25
+ " acp-ws-server --port 8765 -- uv run agentpool serve-acp\n"
26
+ " acp-ws-server -H 0.0.0.0 -p 9000 -- uv run my-agent\n"
27
+ ),
28
+ formatter_class=argparse.RawTextHelpFormatter,
29
+ )
30
+
31
+ parser.add_argument("command", help="Command to spawn the ACP agent.")
32
+ parser.add_argument("args", nargs="*", help="Arguments for the agent command.")
33
+ parser.add_argument(
34
+ "-p",
35
+ "--port",
36
+ type=int,
37
+ default=8765,
38
+ help="Port for the WebSocket server. Default: 8765",
39
+ )
40
+ parser.add_argument(
41
+ "-H",
42
+ "--host",
43
+ default="localhost",
44
+ help="Host to bind the server to. Default: localhost",
45
+ )
46
+ parser.add_argument(
47
+ "--log-level",
48
+ choices=["DEBUG", "INFO", "WARNING", "ERROR"],
49
+ default="INFO",
50
+ help="Log level. Default: INFO",
51
+ )
52
+ parser.add_argument(
53
+ "--cwd",
54
+ default=None,
55
+ help="Working directory for the agent subprocess.",
56
+ )
57
+
58
+ parsed = parser.parse_args()
59
+
60
+ if not parsed.command:
61
+ parser.print_help()
62
+ sys.exit(1)
63
+
64
+ logging.basicConfig(
65
+ level=getattr(logging, parsed.log_level),
66
+ format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
67
+ )
68
+ # Reduce websockets noise
69
+ logging.getLogger("websockets").setLevel(logging.WARNING)
70
+
71
+ from acp.bridge.ws_server import ACPWebSocketServer
72
+
73
+ server = ACPWebSocketServer(
74
+ command=parsed.command,
75
+ args=parsed.args,
76
+ host=parsed.host,
77
+ port=parsed.port,
78
+ cwd=parsed.cwd,
79
+ )
80
+
81
+ print(f"🔗 ACP WebSocket bridge starting on ws://{parsed.host}:{parsed.port}")
82
+ print(f" Agent command: {parsed.command} {' '.join(parsed.args)}")
83
+
84
+ with contextlib.suppress(KeyboardInterrupt):
85
+ anyio.run(server.serve)
86
+
87
+
88
+ if __name__ == "__main__":
89
+ main()
acp/client/connection.py CHANGED
@@ -7,8 +7,35 @@ from typing import TYPE_CHECKING, Any, Self
7
7
 
8
8
  import logfire
9
9
 
10
+ from acp.agent.protocol import Agent
11
+ from acp.connection import Connection
12
+ from acp.exceptions import RequestError
13
+ from acp.schema import (
14
+ AuthenticateResponse,
15
+ CreateTerminalRequest,
16
+ ForkSessionResponse,
17
+ InitializeResponse,
18
+ KillTerminalCommandRequest,
19
+ ListSessionsResponse,
20
+ LoadSessionResponse,
21
+ NewSessionResponse,
22
+ PromptResponse,
23
+ ReadTextFileRequest,
24
+ ReleaseTerminalRequest,
25
+ RequestPermissionRequest,
26
+ ResumeSessionResponse,
27
+ SessionNotification,
28
+ SetSessionConfigOptionResponse,
29
+ SetSessionModelResponse,
30
+ SetSessionModeResponse,
31
+ TerminalOutputRequest,
32
+ WaitForTerminalExitRequest,
33
+ WriteTextFileRequest,
34
+ )
35
+
10
36
 
11
37
  if TYPE_CHECKING:
38
+ from collections.abc import Callable
12
39
  from types import TracebackType
13
40
 
14
41
  from anyio.abc import ByteReceiveStream, ByteSendStream
@@ -31,6 +58,7 @@ if TYPE_CHECKING:
31
58
  ReleaseTerminalResponse,
32
59
  RequestPermissionResponse,
33
60
  ResumeSessionRequest,
61
+ SetSessionConfigOptionRequest,
34
62
  SetSessionModelRequest,
35
63
  SetSessionModeRequest,
36
64
  TerminalOutputResponse,
@@ -38,35 +66,6 @@ if TYPE_CHECKING:
38
66
  WriteTextFileResponse,
39
67
  )
40
68
 
41
- from acp.agent.protocol import Agent
42
- from acp.connection import Connection
43
- from acp.exceptions import RequestError
44
- from acp.schema import (
45
- AuthenticateResponse,
46
- CreateTerminalRequest,
47
- ForkSessionResponse,
48
- InitializeResponse,
49
- KillTerminalCommandRequest,
50
- ListSessionsResponse,
51
- LoadSessionResponse,
52
- NewSessionResponse,
53
- PromptResponse,
54
- ReadTextFileRequest,
55
- ReleaseTerminalRequest,
56
- RequestPermissionRequest,
57
- ResumeSessionResponse,
58
- SessionNotification,
59
- SetSessionModelResponse,
60
- SetSessionModeResponse,
61
- TerminalOutputRequest,
62
- WaitForTerminalExitRequest,
63
- WriteTextFileRequest,
64
- )
65
-
66
-
67
- if TYPE_CHECKING:
68
- from collections.abc import Callable
69
-
70
69
 
71
70
  class ClientSideConnection(Agent):
72
71
  """Client-side connection.
@@ -151,6 +150,16 @@ class ClientSideConnection(Agent):
151
150
  payload = resp if isinstance(resp, dict) else {}
152
151
  return SetSessionModelResponse.model_validate(payload)
153
152
 
153
+ async def set_session_config_option(
154
+ self, params: SetSessionConfigOptionRequest
155
+ ) -> SetSessionConfigOptionResponse:
156
+ dct = params.model_dump(
157
+ mode="json", by_alias=True, exclude_none=True, exclude_defaults=True
158
+ )
159
+ resp = await self._conn.send_request("session/set_config_option", dct)
160
+ payload = resp if isinstance(resp, dict) else {}
161
+ return SetSessionConfigOptionResponse.model_validate(payload)
162
+
154
163
  async def authenticate(self, params: AuthenticateRequest) -> AuthenticateResponse:
155
164
  dct = params.model_dump(
156
165
  mode="json", by_alias=True, exclude_none=True, exclude_defaults=True
@@ -9,9 +9,10 @@ from __future__ import annotations
9
9
  from pathlib import Path
10
10
  from typing import TYPE_CHECKING, Any
11
11
 
12
+ import structlog
13
+
12
14
  from acp.client import Client
13
15
  from acp.schema import ReadTextFileResponse, RequestPermissionResponse, WriteTextFileResponse
14
- from agentpool import log
15
16
 
16
17
 
17
18
  if TYPE_CHECKING:
@@ -34,7 +35,7 @@ if TYPE_CHECKING:
34
35
  WriteTextFileRequest,
35
36
  )
36
37
 
37
- logger = log.get_logger(__name__)
38
+ logger = structlog.get_logger(__name__)
38
39
 
39
40
 
40
41
  class DefaultACPClient(Client):
@@ -12,6 +12,7 @@ from typing import TYPE_CHECKING, Any
12
12
  import uuid
13
13
 
14
14
  from anyenv import ProcessManager
15
+ import structlog
15
16
 
16
17
  from acp.client.protocol import Client
17
18
  from acp.schema import (
@@ -24,7 +25,6 @@ from acp.schema import (
24
25
  WaitForTerminalExitResponse,
25
26
  WriteTextFileResponse,
26
27
  )
27
- from agentpool.log import get_logger
28
28
 
29
29
 
30
30
  if TYPE_CHECKING:
@@ -40,7 +40,7 @@ if TYPE_CHECKING:
40
40
  WriteTextFileRequest,
41
41
  )
42
42
 
43
- logger = get_logger(__name__)
43
+ logger = structlog.get_logger(__name__)
44
44
 
45
45
 
46
46
  class HeadlessACPClient(Client):
acp/connection.py CHANGED
@@ -15,6 +15,7 @@ import anyenv
15
15
  import anyio
16
16
  from anyio.streams.text import TextReceiveStream
17
17
  from pydantic import BaseModel, ValidationError
18
+ import structlog
18
19
 
19
20
  from acp.exceptions import RequestError
20
21
  from acp.task import (
@@ -31,7 +32,6 @@ from acp.task import (
31
32
  RpcTaskKind,
32
33
  TaskSupervisor,
33
34
  )
34
- from agentpool import log
35
35
 
36
36
 
37
37
  if TYPE_CHECKING:
@@ -49,7 +49,7 @@ DispatcherFactory = Callable[
49
49
  ]
50
50
 
51
51
 
52
- logger = log.get_logger(__name__)
52
+ logger = structlog.get_logger(__name__)
53
53
 
54
54
 
55
55
  StreamDirection = Literal["incoming", "outgoing"]
acp/notifications.py CHANGED
@@ -6,6 +6,7 @@ from collections.abc import Sequence
6
6
  from typing import TYPE_CHECKING, Any, assert_never
7
7
 
8
8
  from pydantic_ai import ModelRequest, ModelResponse, ToolReturnPart, UserPromptPart
9
+ import structlog
9
10
 
10
11
  from acp.schema import (
11
12
  AgentMessageChunk,
@@ -33,7 +34,7 @@ from acp.schema import (
33
34
  from acp.schema.tool_call import ToolCallLocation
34
35
  from acp.tool_call_reporter import ToolCallReporter
35
36
  from acp.utils import generate_tool_title, infer_tool_kind, to_acp_content_blocks
36
- from agentpool.log import get_logger
37
+ from agentpool.utils.pydantic_ai_helpers import safe_args_as_dict
37
38
 
38
39
 
39
40
  if TYPE_CHECKING:
@@ -52,7 +53,7 @@ if TYPE_CHECKING:
52
53
 
53
54
  ContentType = Sequence[ToolCallContent | str]
54
55
 
55
- logger = get_logger(__name__)
56
+ logger = structlog.get_logger(__name__)
56
57
 
57
58
 
58
59
  class ACPNotifications:
@@ -133,11 +134,11 @@ class ACPNotifications:
133
134
  async def tool_call(
134
135
  self,
135
136
  tool_name: str,
137
+ tool_call_id: str,
136
138
  *,
137
139
  tool_input: dict[str, Any],
138
140
  tool_output: Any,
139
141
  status: ToolCallStatus = "completed",
140
- tool_call_id: str | None = None,
141
142
  ) -> None:
142
143
  """Send tool execution as ACP tool call update.
143
144
 
@@ -175,11 +176,10 @@ class ACPNotifications:
175
176
 
176
177
  # Generate a descriptive title from tool name and inputs
177
178
  title = generate_tool_title(tool_name, tool_input)
178
-
179
179
  # Use appropriate notification type based on status
180
180
  if status == "pending":
181
181
  await self.tool_call_start(
182
- tool_call_id=tool_call_id or f"{tool_name}_{hash(str(tool_input))}",
182
+ tool_call_id=tool_call_id,
183
183
  title=title,
184
184
  kind=infer_tool_kind(tool_name),
185
185
  locations=locations or None,
@@ -189,7 +189,7 @@ class ACPNotifications:
189
189
  else:
190
190
  # For in_progress, completed, and failed statuses
191
191
  await self.tool_call_progress(
192
- tool_call_id=tool_call_id or f"{tool_name}_{hash(str(tool_input))}",
192
+ tool_call_id=tool_call_id,
193
193
  title=title,
194
194
  status=status,
195
195
  locations=locations or None,
@@ -606,36 +606,24 @@ class ACPNotifications:
606
606
  match block:
607
607
  case TextContentBlock(text=text):
608
608
  await self.send_user_message(text)
609
- case ImageContentBlock() as img_block:
609
+ case ImageContentBlock(annotations=annots) as img_block:
610
610
  await self.send_user_image(
611
611
  data=img_block.data,
612
612
  mime_type=img_block.mime_type,
613
613
  uri=img_block.uri,
614
- audience=img_block.annotations.audience
615
- if img_block.annotations
616
- else None,
617
- last_modified=img_block.annotations.last_modified
618
- if img_block.annotations
619
- else None,
620
- priority=img_block.annotations.priority
621
- if img_block.annotations
622
- else None,
614
+ audience=annots.audience if annots else None,
615
+ last_modified=annots.last_modified if annots else None,
616
+ priority=annots.priority if annots else None,
623
617
  )
624
- case AudioContentBlock() as audio_block:
618
+ case AudioContentBlock(annotations=annots) as audio_block:
625
619
  await self.send_user_audio(
626
620
  data=audio_block.data,
627
621
  mime_type=audio_block.mime_type,
628
- audience=audio_block.annotations.audience
629
- if audio_block.annotations
630
- else None,
631
- last_modified=audio_block.annotations.last_modified
632
- if audio_block.annotations
633
- else None,
634
- priority=audio_block.annotations.priority
635
- if audio_block.annotations
636
- else None,
622
+ audience=annots.audience if annots else None,
623
+ last_modified=annots.last_modified if annots else None,
624
+ priority=annots.priority if annots else None,
637
625
  )
638
- case ResourceContentBlock() as resource_block:
626
+ case ResourceContentBlock(annotations=annots) as resource_block:
639
627
  await self.send_user_resource(
640
628
  uri=resource_block.uri,
641
629
  name=resource_block.name,
@@ -643,15 +631,9 @@ class ACPNotifications:
643
631
  mime_type=resource_block.mime_type,
644
632
  size=resource_block.size,
645
633
  title=resource_block.title,
646
- audience=resource_block.annotations.audience
647
- if resource_block.annotations
648
- else None,
649
- last_modified=resource_block.annotations.last_modified
650
- if resource_block.annotations
651
- else None,
652
- priority=resource_block.annotations.priority
653
- if resource_block.annotations
654
- else None,
634
+ audience=annots.audience if annots else None,
635
+ last_modified=annots.last_modified if annots else None,
636
+ priority=annots.priority if annots else None,
655
637
  )
656
638
  case EmbeddedResourceContentBlock() as embedded_block:
657
639
  # Handle embedded resources with proper
@@ -677,14 +659,12 @@ class ACPNotifications:
677
659
  tool_input = self._tool_call_inputs.get(tool_call_id, {})
678
660
  await self.tool_call(
679
661
  tool_name=tool_name,
662
+ tool_call_id=tool_call_id,
680
663
  tool_input=tool_input,
681
664
  tool_output=converted_content,
682
665
  status="completed",
683
- tool_call_id=tool_call_id,
684
666
  )
685
- # Clean up stored input
686
667
  self._tool_call_inputs.pop(tool_call_id, None)
687
-
688
668
  case _:
689
669
  typ = type(part).__name__
690
670
  self.log.debug("Unhandled request part type", part_type=typ)
@@ -703,7 +683,7 @@ class ACPNotifications:
703
683
 
704
684
  case ToolCallPart(tool_call_id=tool_call_id):
705
685
  # Store tool call inputs for later use with ToolReturnPart
706
- tool_input = part.args_as_dict()
686
+ tool_input = safe_args_as_dict(part)
707
687
  self._tool_call_inputs[tool_call_id] = tool_input
708
688
  # Skip sending notification - ACP protocol overrides previous
709
689
  # tool call state
@@ -753,16 +733,6 @@ class ACPNotifications:
753
733
  notification = SessionNotification(session_id=self.id, update=update)
754
734
  await self.client.session_update(notification) # pyright: ignore[reportArgumentType] # ty: ignore[invalid-argument-type]
755
735
 
756
- # async def update_session_model(self, model_id: str) -> None:
757
- # """Send a session model update notification.
758
-
759
- # Args:
760
- # model_id: Unique identifier for the model
761
- # """
762
- # update = CurrentModelUpdate(current_model_id=model_id)
763
- # notification = SessionNotification(session_id=self.id, update=update)
764
- # await self.client.session_update(notification)
765
-
766
736
  async def send_agent_audio(
767
737
  self,
768
738
  data: str | bytes,
acp/schema/__init__.py CHANGED
@@ -96,6 +96,7 @@ from acp.schema.notifications import (
96
96
  from acp.schema.session_state import (
97
97
  ModelInfo,
98
98
  SessionConfigOption,
99
+ SessionConfigOptionCategory,
99
100
  SessionConfigSelect,
100
101
  SessionConfigSelectGroup,
101
102
  SessionConfigSelectOption,
@@ -222,6 +223,7 @@ __all__ = [
222
223
  "ResumeSessionResponse",
223
224
  "SessionCapabilities",
224
225
  "SessionConfigOption",
226
+ "SessionConfigOptionCategory",
225
227
  "SessionConfigSelect",
226
228
  "SessionConfigSelectGroup",
227
229
  "SessionConfigSelectOption",
@@ -9,6 +9,7 @@ from acp.schema.base import Response
9
9
  from acp.schema.capabilities import AgentCapabilities
10
10
  from acp.schema.common import AuthMethod, Implementation # noqa: TC001
11
11
  from acp.schema.session_state import ( # noqa: TC001
12
+ SessionConfigOption,
12
13
  SessionInfo,
13
14
  SessionModelState,
14
15
  SessionModeState,
@@ -58,6 +59,14 @@ class NewSessionResponse(Response):
58
59
  See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes)
59
60
  """
60
61
 
62
+ config_options: Sequence[SessionConfigOption] | None = None
63
+ """**UNSTABLE**
64
+
65
+ Configuration options for this session.
66
+
67
+ See RFD: Session Config Options
68
+ """
69
+
61
70
  session_id: str
62
71
  """Unique identifier for the created session.
63
72
 
@@ -82,6 +91,9 @@ class LoadSessionResponse(Response):
82
91
  See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes)
83
92
  """
84
93
 
94
+ config_options: Sequence[SessionConfigOption] = []
95
+ """The full list of config options with updated values."""
96
+
85
97
 
86
98
  class ForkSessionResponse(Response):
87
99
  """**UNSTABLE**: This capability is not part of the spec yet.
@@ -106,6 +118,9 @@ class ForkSessionResponse(Response):
106
118
  session_id: str
107
119
  """Unique identifier for the newly created forked session."""
108
120
 
121
+ config_options: Sequence[SessionConfigOption] = []
122
+ """The full list of config options with updated values."""
123
+
109
124
 
110
125
  class ResumeSessionResponse(Response):
111
126
  """**UNSTABLE**: This capability is not part of the spec yet.
@@ -127,6 +142,9 @@ class ResumeSessionResponse(Response):
127
142
  See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes)
128
143
  """
129
144
 
145
+ config_options: Sequence[SessionConfigOption] = []
146
+ """The full list of config options with updated values."""
147
+
130
148
 
131
149
  class SetSessionModeResponse(Response):
132
150
  """Response to `session/set_mode` method."""
@@ -135,6 +153,9 @@ class SetSessionModeResponse(Response):
135
153
  class SetSessionConfigOptionResponse(Response):
136
154
  """Response to `session/set_config_option` method."""
137
155
 
156
+ config_options: Sequence[SessionConfigOption] = []
157
+ """The full list of config options with updated values."""
158
+
138
159
 
139
160
  class PromptResponse(Response):
140
161
  """Response from processing a user prompt.
@@ -33,7 +33,7 @@ class NewSessionRequest(Request):
33
33
  cwd: str
34
34
  """The working directory for this session. Must be an absolute path."""
35
35
 
36
- mcp_servers: Sequence[McpServer]
36
+ mcp_servers: Sequence[McpServer] | None = None
37
37
  """List of MCP (Model Context Protocol) servers the agent should connect to."""
38
38
 
39
39
 
@@ -48,7 +48,7 @@ class LoadSessionRequest(Request):
48
48
  cwd: str
49
49
  """The working directory for this session."""
50
50
 
51
- mcp_servers: Sequence[McpServer]
51
+ mcp_servers: Sequence[McpServer] | None = None
52
52
  """List of MCP servers to connect to for this session."""
53
53
 
54
54
  session_id: str
@@ -176,7 +176,7 @@ class SetSessionConfigOptionRequest(Request):
176
176
  session_id: str
177
177
  """The ID of the session to set the config option for."""
178
178
 
179
- value_id: str
179
+ value: str = Field(serialization_alias="valueId")
180
180
  """The ID of the value to set for this configuration option."""
181
181
 
182
182
 
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from collections.abc import Sequence
6
- from typing import Annotated, Literal
6
+ from typing import Literal
7
7
 
8
8
  from pydantic import Field
9
9
 
@@ -21,6 +21,24 @@ SessionConfigGroupId = str
21
21
  """Unique identifier for a group of values within a configuration option."""
22
22
 
23
23
 
24
+ SessionConfigOptionCategory = Literal["mode", "model", "thought_level", "other"]
25
+ """**UNSTABLE**: This capability is not part of the spec yet.
26
+
27
+ Semantic category for a session configuration option.
28
+
29
+ This is intended to help Clients distinguish broadly common selectors (e.g. model selector vs
30
+ session mode selector vs thought/reasoning level) for UX purposes (keyboard shortcuts, icons,
31
+ placement). It MUST NOT be required for correctness. Clients MUST handle missing or unknown
32
+ categories gracefully (treat as `other`).
33
+
34
+ Values:
35
+ - "mode": Session mode selector
36
+ - "model": Model selector
37
+ - "thought_level": Thought/reasoning level selector
38
+ - "other": Unknown / uncategorized selector
39
+ """
40
+
41
+
24
42
  class ModelInfo(AnnotatedObject):
25
43
  """**UNSTABLE**: This capability is not part of the spec yet.
26
44
 
@@ -98,63 +116,79 @@ class SessionInfo(AnnotatedObject):
98
116
  class SessionConfigSelectOption(AnnotatedObject):
99
117
  """A possible value for a configuration selector."""
100
118
 
101
- id: SessionConfigValueId
102
- """Unique identifier for this value."""
119
+ value: SessionConfigValueId
120
+ """Unique identifier for this option value."""
103
121
 
104
- label: str
105
- """Human-readable label for this value."""
122
+ name: str
123
+ """Human-readable label for this option value."""
106
124
 
107
125
  description: str | None = None
108
- """Optional description explaining this value."""
126
+ """Optional description for this option value."""
109
127
 
110
128
 
111
129
  class SessionConfigSelectGroup(AnnotatedObject):
112
130
  """A group of possible values for a configuration selector."""
113
131
 
114
- id: SessionConfigGroupId
132
+ group: SessionConfigGroupId
115
133
  """Unique identifier for this group."""
116
134
 
117
- label: str
135
+ name: str
118
136
  """Human-readable label for this group."""
119
137
 
120
138
  options: Sequence[SessionConfigSelectOption]
121
- """The options within this group."""
139
+ """The set of option values in this group."""
122
140
 
123
141
 
124
- SessionConfigSelectOptions = Sequence[SessionConfigSelectOption | SessionConfigSelectGroup]
142
+ SessionConfigSelectOptions = (
143
+ Sequence[SessionConfigSelectOption] | Sequence[SessionConfigSelectGroup]
144
+ )
125
145
  """The possible values for a configuration selector, optionally organized into groups."""
126
146
 
127
147
 
128
148
  class SessionConfigSelect(AnnotatedObject):
129
- """A configuration option that allows selecting a single value from a list.
149
+ """A single-value selector (dropdown) session configuration option payload."""
150
+
151
+ current_value: SessionConfigValueId
152
+ """The currently selected value."""
153
+
154
+ options: SessionConfigSelectOptions
155
+ """The set of selectable options."""
130
156
 
131
- Similar to a dropdown/select UI element.
132
- """
157
+
158
+ class SessionConfigKind(AnnotatedObject):
159
+ """Type-specific session configuration option payload."""
133
160
 
134
161
  type: Literal["select"] = Field(default="select", init=False)
135
162
  """Discriminator for the config option type."""
136
163
 
164
+ # Flattened SessionConfigSelect fields
165
+ current_value: SessionConfigValueId
166
+ """The currently selected value."""
167
+
168
+ options: SessionConfigSelectOptions
169
+ """The set of selectable options."""
170
+
171
+
172
+ class SessionConfigOption(AnnotatedObject):
173
+ """A session configuration option selector and its current state."""
174
+
137
175
  id: SessionConfigId
138
- """Unique identifier for this configuration option."""
176
+ """Unique identifier for the configuration option."""
139
177
 
140
- label: str
141
- """Human-readable label for this option."""
178
+ name: str
179
+ """Human-readable label for the option."""
142
180
 
143
181
  description: str | None = None
144
- """Optional description explaining this option."""
182
+ """Optional description for the Client to display to the user."""
145
183
 
146
- options: SessionConfigSelectOptions
147
- """The possible values for this option."""
148
-
149
- value: SessionConfigValueId
150
- """The currently selected value ID."""
184
+ category: SessionConfigOptionCategory | None = None
185
+ """Optional semantic category for this option (UX only)."""
151
186
 
187
+ type: Literal["select"] = Field(default="select", init=False)
188
+ """Discriminator for the config option type (flattened from kind)."""
152
189
 
153
- SessionConfigOption = Annotated[
154
- SessionConfigSelect,
155
- Field(discriminator="type"),
156
- ]
157
- """A session configuration option.
190
+ current_value: SessionConfigValueId
191
+ """The currently selected value (flattened from kind.select)."""
158
192
 
159
- Currently only supports select-type options, but designed for extensibility.
160
- """
193
+ options: SessionConfigSelectOptions
194
+ """The set of selectable options (flattened from kind.select)."""