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
@@ -16,31 +16,27 @@ from acp.schema import (
16
16
  NewSessionResponse,
17
17
  PromptResponse,
18
18
  ResumeSessionResponse,
19
+ SessionConfigOption,
20
+ SessionConfigSelectOption,
19
21
  SessionInfo,
22
+ SessionMode,
20
23
  SessionModelState,
21
24
  SessionModeState,
25
+ SetSessionConfigOptionResponse,
22
26
  SetSessionModelRequest,
23
27
  SetSessionModelResponse,
24
28
  SetSessionModeRequest,
25
29
  SetSessionModeResponse,
26
30
  )
27
- from agentpool import Agent
28
31
  from agentpool.log import get_logger
29
32
  from agentpool.utils.tasks import TaskManager
30
- from agentpool_server.acp_server.converters import (
31
- # agent_to_mode, # TODO: Re-enable when supporting agent switching via modes
32
- get_confirmation_modes,
33
- mode_id_to_confirmation_mode,
34
- )
35
33
  from agentpool_server.acp_server.session_manager import ACPSessionManager
36
34
 
37
35
 
38
36
  if TYPE_CHECKING:
39
- from collections.abc import Sequence
40
-
41
37
  from pydantic_ai import ModelRequest, ModelResponse
42
38
 
43
- from acp import AgentSideConnection, Client
39
+ from acp import Client
44
40
  from acp.schema import (
45
41
  AuthenticateRequest,
46
42
  CancelNotification,
@@ -53,6 +49,7 @@ if TYPE_CHECKING:
53
49
  NewSessionRequest,
54
50
  PromptRequest,
55
51
  ResumeSessionRequest,
52
+ SetSessionConfigOptionRequest,
56
53
  SetSessionModelRequest,
57
54
  SetSessionModeRequest,
58
55
  )
@@ -63,62 +60,155 @@ if TYPE_CHECKING:
63
60
  logger = get_logger(__name__)
64
61
 
65
62
 
66
- # Claude Code model definitions - simple IDs that the SDK understands
67
- CLAUDE_CODE_MODELS: list[ACPModelInfo] = [
68
- ACPModelInfo(
69
- model_id="opus",
70
- name="Claude Opus",
71
- description="Claude Opus - most capable model",
72
- ),
73
- ACPModelInfo(
74
- model_id="sonnet",
75
- name="Claude Sonnet",
76
- description="Claude Sonnet - balanced performance and speed",
77
- ),
78
- ACPModelInfo(
79
- model_id="haiku",
80
- name="Claude Haiku",
81
- description="Claude Haiku - fast and cost-effective",
82
- ),
83
- ]
84
-
85
-
86
- def create_claude_code_model_state(current_model: str | None = None) -> SessionModelState:
87
- """Create SessionModelState for Claude Code agents.
63
+ async def get_session_model_state(
64
+ agent: Any, current_model: str | None = None
65
+ ) -> SessionModelState | None:
66
+ """Get SessionModelState from an agent using its get_available_models() method.
67
+
68
+ Converts tokonomics ModelInfo to ACP ModelInfo format.
88
69
 
89
70
  Args:
90
- current_model: Currently active model ID (e.g., "sonnet")
71
+ agent: Any agent with get_available_models() method
72
+ current_model: Currently active model ID (defaults to first available)
91
73
 
92
74
  Returns:
93
- SessionModelState with Claude Code models
75
+ SessionModelState with all available models, None if no models available
94
76
  """
95
- model_ids = [m.model_id for m in CLAUDE_CODE_MODELS]
96
- current = current_model if current_model in model_ids else "sonnet"
97
- return SessionModelState(available_models=CLAUDE_CODE_MODELS, current_model_id=current)
77
+ from agentpool.agents.base_agent import BaseAgent
98
78
 
79
+ if not isinstance(agent, BaseAgent):
80
+ return None
99
81
 
100
- def create_session_model_state(
101
- available_models: Sequence[ACPModelInfo], current_model: str | None = None
102
- ) -> SessionModelState | None:
103
- """Create a SessionModelState from available ACP models.
82
+ try:
83
+ toko_models = await agent.get_available_models()
84
+ except Exception:
85
+ logger.exception("Failed to get available models from agent")
86
+ return None
87
+
88
+ if not toko_models:
89
+ return None
90
+
91
+ # Convert tokonomics ModelInfo to ACP ModelInfo
92
+ acp_models: list[ACPModelInfo] = []
93
+ for toko in toko_models:
94
+ # Use id_override if set (e.g., "opus" for Claude Code), otherwise use id
95
+ model_id = toko.id_override if toko.id_override else toko.id
96
+ info = ACPModelInfo(model_id=model_id, name=toko.name, description=toko.description or "")
97
+ acp_models.append(info)
98
+ if not acp_models:
99
+ return None
100
+
101
+ # Ensure current model is in the list
102
+ all_ids = [model.model_id for model in acp_models]
103
+ if current_model and current_model not in all_ids:
104
+ # Add current model to the list so the UI shows it
105
+ desc = "Currently configured model"
106
+ model_info = ACPModelInfo(model_id=current_model, name=current_model, description=desc)
107
+ acp_models.insert(0, model_info)
108
+ current_model_id = current_model
109
+ else:
110
+ current_model_id = current_model if current_model in all_ids else all_ids[0]
111
+
112
+ return SessionModelState(available_models=acp_models, current_model_id=current_model_id)
113
+
114
+
115
+ async def get_session_mode_state(agent: Any) -> SessionModeState | None:
116
+ """Get SessionModeState from an agent using its get_modes() method.
117
+
118
+ Converts agentpool ModeCategory to ACP SessionModeState format.
119
+ Uses the first category that looks like permissions (not model).
104
120
 
105
121
  Args:
106
- available_models: List of ACP ModelInfo objects (already converted from tokonomics)
107
- current_model: The currently active model (defaults to first available)
122
+ agent: Any agent with get_modes() method
108
123
 
109
124
  Returns:
110
- SessionModelState with all available models, None if no models provided
125
+ SessionModeState from agent's modes, None if no modes available
111
126
  """
112
- if not available_models:
127
+ from agentpool.agents.base_agent import BaseAgent
128
+
129
+ if not isinstance(agent, BaseAgent):
130
+ return None
131
+
132
+ try:
133
+ mode_categories = await agent.get_modes()
134
+ except Exception:
135
+ logger.exception("Failed to get modes from agent")
136
+ return None
137
+
138
+ if not mode_categories:
139
+ return None
140
+
141
+ # Find the permissions category (not model)
142
+ category = next((c for c in mode_categories if c.id != "model"), None)
143
+ if not category:
113
144
  return None
114
- # Use first model as current if not specified or not found
115
- all_ids = [model.model_id for model in available_models]
116
- current_model_id = current_model if current_model in all_ids else all_ids[0]
117
- return SessionModelState(
118
- available_models=list(available_models), current_model_id=current_model_id
145
+
146
+ # Convert ModeInfo to ACP SessionMode
147
+ acp_modes = [
148
+ SessionMode(
149
+ id=mode.id,
150
+ name=mode.name,
151
+ description=mode.description,
152
+ )
153
+ for mode in category.available_modes
154
+ ]
155
+
156
+ return SessionModeState(
157
+ available_modes=acp_modes,
158
+ current_mode_id=category.current_mode_id,
119
159
  )
120
160
 
121
161
 
162
+ async def get_session_config_options(agent: Any) -> list[SessionConfigOption]:
163
+ """Get SessionConfigOptions from an agent using its get_modes() method.
164
+
165
+ Converts all agentpool ModeCategories to ACP SessionConfigOption format.
166
+
167
+ Args:
168
+ agent: Any agent with get_modes() method
169
+
170
+ Returns:
171
+ List of SessionConfigOption from agent's mode categories
172
+ """
173
+ from agentpool.agents.base_agent import BaseAgent
174
+
175
+ if not isinstance(agent, BaseAgent):
176
+ return []
177
+
178
+ try:
179
+ mode_categories = await agent.get_modes()
180
+ except Exception:
181
+ logger.exception("Failed to get modes from agent")
182
+ return []
183
+
184
+ if not mode_categories:
185
+ return []
186
+
187
+ # Convert each ModeCategory to a SessionConfigOption
188
+ config_options: list[SessionConfigOption] = []
189
+ for category in mode_categories:
190
+ options = [
191
+ SessionConfigSelectOption(
192
+ value=mode.id,
193
+ name=mode.name,
194
+ description=mode.description,
195
+ )
196
+ for mode in category.available_modes
197
+ ]
198
+ config_options.append(
199
+ SessionConfigOption(
200
+ id=category.id,
201
+ name=category.name,
202
+ description=None,
203
+ category=category.category,
204
+ current_value=category.current_mode_id,
205
+ options=options,
206
+ )
207
+ )
208
+
209
+ return config_options
210
+
211
+
122
212
  @dataclass
123
213
  class AgentPoolACPAgent(ACPAgent):
124
214
  """Implementation of ACP Agent protocol interface for AgentPool.
@@ -129,7 +219,7 @@ class AgentPoolACPAgent(ACPAgent):
129
219
 
130
220
  PROTOCOL_VERSION: ClassVar = 1
131
221
 
132
- connection: AgentSideConnection
222
+ client: Client
133
223
  """ACP connection for client communication."""
134
224
 
135
225
  agent_pool: AgentPool[Any]
@@ -137,9 +227,6 @@ class AgentPoolACPAgent(ACPAgent):
137
227
 
138
228
  _: KW_ONLY
139
229
 
140
- available_models: Sequence[ACPModelInfo] = field(default_factory=list)
141
- """List of available ACP ModelInfo objects (converted from tokonomics at startup)."""
142
-
143
230
  file_access: bool = True
144
231
  """Whether agent can access filesystem."""
145
232
 
@@ -160,7 +247,6 @@ class AgentPoolACPAgent(ACPAgent):
160
247
 
161
248
  def __post_init__(self) -> None:
162
249
  """Initialize derived attributes and setup after field assignment."""
163
- self.client: Client = self.connection
164
250
  self.client_capabilities: ClientCapabilities | None = None
165
251
  self.client_info: Implementation | None = None
166
252
  self.session_manager = ACPSessionManager(pool=self.agent_pool)
@@ -176,17 +262,12 @@ class AgentPoolACPAgent(ACPAgent):
176
262
 
177
263
  async def initialize(self, params: InitializeRequest) -> InitializeResponse:
178
264
  """Initialize the agent and negotiate capabilities."""
179
- logger.info("Initializing ACP agent implementation")
180
265
  version = min(params.protocol_version, self.PROTOCOL_VERSION)
181
266
  self.client_capabilities = params.client_capabilities
182
267
  self.client_info = params.client_info
183
- logger.info(
184
- "Client capabilities",
185
- capabilities=self.client_capabilities,
186
- client_info=self.client_info,
187
- )
268
+ logger.info("Client info", request=params.model_dump_json())
188
269
  self._initialized = True
189
- response = InitializeResponse.create(
270
+ return InitializeResponse.create(
190
271
  protocol_version=version,
191
272
  name="agentpool",
192
273
  title="AgentPool",
@@ -199,10 +280,8 @@ class AgentPoolACPAgent(ACPAgent):
199
280
  embedded_context_prompts=True,
200
281
  image_prompts=True,
201
282
  )
202
- logger.info("ACP agent initialized successfully", response=response)
203
- return response
204
283
 
205
- async def new_session(self, params: NewSessionRequest) -> NewSessionResponse: # noqa: PLR0915
284
+ async def new_session(self, params: NewSessionRequest) -> NewSessionResponse:
206
285
  """Create a new session."""
207
286
  if not self._initialized:
208
287
  raise RuntimeError("Agent not initialized")
@@ -230,48 +309,26 @@ class AgentPoolACPAgent(ACPAgent):
230
309
  client_info=self.client_info,
231
310
  )
232
311
 
233
- # Get mode information - pass through from ACPAgent or use our confirmation modes
312
+ # Get mode and model information from the agent
234
313
  from agentpool.agents.acp_agent import ACPAgent as ACPAgentClient
235
314
 
236
- if session := self.session_manager.get_session(session_id):
237
- if isinstance(session.agent, ACPAgentClient):
238
- # Pass through nested agent's modes (e.g., Claude Code's modes)
239
- if session.agent._state and session.agent._state.modes:
240
- state = session.agent._state.modes
241
- modes = state.available_modes
242
- else:
243
- # Fallback to our confirmation modes if nested agent has none
244
- modes = get_confirmation_modes()
245
- state = SessionModeState(current_mode_id="default", available_modes=modes)
246
- else:
247
- # Native Agent - use our tool confirmation modes
248
- modes = get_confirmation_modes()
249
- state = SessionModeState(current_mode_id="default", available_modes=modes)
250
- else:
251
- modes = get_confirmation_modes()
252
- state = SessionModeState(current_mode_id="default", available_modes=modes)
253
- # TODO: Re-enable agent switching via modes when needed:
254
- # modes = [agent_to_mode(agent) for agent in self.agent_pool.all_agents.values()]
255
- # state = SessionModeState(current_mode_id=default_name, available_modes=modes)
315
+ state: SessionModeState | None = None
316
+ models: SessionModelState | None = None
317
+ config_options: list[SessionConfigOption] = []
256
318
 
257
- # Get model information from the default agent
258
319
  if session := self.session_manager.get_session(session_id):
259
- from agentpool.agents.claude_code_agent import ClaudeCodeAgent
260
-
261
- if isinstance(session.agent, ClaudeCodeAgent):
262
- # Claude Code uses simple model IDs (sonnet, opus, haiku)
263
- models = create_claude_code_model_state(session.agent.model_name)
264
- elif isinstance(session.agent, ACPAgentClient):
265
- # Nested ACP agent - pass through its model state directly
266
- if session.agent._state and session.agent._state.models:
320
+ if isinstance(session.agent, ACPAgentClient):
321
+ # Nested ACP agent - pass through its state directly
322
+ if session.agent._state:
267
323
  models = session.agent._state.models
268
- else:
269
- models = None
324
+ state = session.agent._state.modes
325
+ # Also get config_options from nested agent
326
+ config_options = await get_session_config_options(session.agent)
270
327
  else:
271
- current_model = session.agent.model_name
272
- models = create_session_model_state(self.available_models, current_model)
273
- else:
274
- models = None
328
+ # Use unified helpers for all other agents
329
+ models = await get_session_model_state(session.agent, session.agent.model_name)
330
+ state = await get_session_mode_state(session.agent)
331
+ config_options = await get_session_config_options(session.agent)
275
332
  except Exception:
276
333
  logger.exception("Failed to create new session")
277
334
  raise
@@ -285,8 +342,14 @@ class AgentPoolACPAgent(ACPAgent):
285
342
  if self.load_skills:
286
343
  coro_4 = session.init_client_skills()
287
344
  self.tasks.create_task(coro_4, name=f"init_client_skills_{session_id}")
288
- logger.info("Created session", session_id=session_id, agent_count=len(modes))
289
- return NewSessionResponse(session_id=session_id, modes=state, models=models)
345
+ logger.info("Created session", session_id=session_id)
346
+
347
+ return NewSessionResponse(
348
+ session_id=session_id,
349
+ modes=state,
350
+ models=models,
351
+ config_options=config_options if config_options else None,
352
+ )
290
353
 
291
354
  async def load_session(self, params: LoadSessionRequest) -> LoadSessionResponse:
292
355
  """Load an existing session from storage.
@@ -298,13 +361,14 @@ class AgentPoolACPAgent(ACPAgent):
298
361
  4. Replay conversation history via ACP notifications
299
362
  5. Return session state (modes, models)
300
363
  """
364
+ from agentpool.agents.acp_agent import ACPAgent as ACPAgentClient
365
+
301
366
  if not self._initialized:
302
367
  raise RuntimeError("Agent not initialized")
303
368
 
304
369
  try:
305
370
  # First check if session is already active
306
371
  session = self.session_manager.get_session(params.session_id)
307
-
308
372
  if not session:
309
373
  # Try to resume from storage
310
374
  msg = "Attempting to resume session from storage"
@@ -331,46 +395,30 @@ class AgentPoolACPAgent(ACPAgent):
331
395
  session.mcp_servers = params.mcp_servers
332
396
  await session.initialize_mcp_servers()
333
397
 
334
- # Build response with current session state
335
- # Get mode information - pass through from ACPAgent or use our confirmation modes
336
- from agentpool.agents.acp_agent import ACPAgent as ACPAgentClient
398
+ mode_state: SessionModeState | None = None
399
+ models: SessionModelState | None = None
400
+ config_opts: list[SessionConfigOption] = []
337
401
 
338
402
  if isinstance(session.agent, ACPAgentClient):
339
- # Pass through nested agent's modes (e.g., Claude Code's modes)
340
- if session.agent._state and session.agent._state.modes:
403
+ # Nested ACP agent - pass through its state directly
404
+ if session.agent._state:
341
405
  mode_state = session.agent._state.modes
342
- else:
343
- # Fallback to our confirmation modes if nested agent has none
344
- modes = get_confirmation_modes()
345
- mode_state = SessionModeState(current_mode_id="default", available_modes=modes)
346
- else:
347
- # Native Agent - use our tool confirmation modes
348
- modes = get_confirmation_modes()
349
- mode_state = SessionModeState(current_mode_id="default", available_modes=modes)
350
- # TODO: Re-enable agent switching via modes when needed:
351
- # modes = [agent_to_mode(agent) for agent in self.agent_pool.all_agents.values()]
352
- # mode_state = SessionModeState(
353
- # current_mode_id=session.current_agent_name,
354
- # available_modes=modes,
355
- # )
356
-
357
- # Get model information based on agent type
358
- from agentpool.agents.claude_code_agent import ClaudeCodeAgent
359
-
360
- models: SessionModelState | None
361
- if session.agent and isinstance(session.agent, ClaudeCodeAgent):
362
- # Claude Code uses simple model IDs (sonnet, opus, haiku)
363
- models = create_claude_code_model_state(session.agent.model_name)
406
+ models = session.agent._state.models
407
+ config_opts = await get_session_config_options(session.agent)
408
+ elif session.agent:
409
+ # Use unified helpers for all other agents
410
+ mode_state = await get_session_mode_state(session.agent)
411
+ models = await get_session_model_state(session.agent, session.agent.model_name)
412
+ config_opts = await get_session_config_options(session.agent)
364
413
  else:
365
- current_model = session.agent.model_name if session.agent else None
366
- models = create_session_model_state(self.available_models, current_model)
414
+ models = None
367
415
  # Schedule post-load initialization tasks
368
416
  self.tasks.create_task(session.send_available_commands_update())
369
417
  self.tasks.create_task(session.init_project_context())
370
418
  # Replay conversation history via ACP notifications
371
419
  self.tasks.create_task(self._replay_conversation_history(session))
372
420
  logger.info("Session loaded successfully", agent=session.current_agent_name)
373
- return LoadSessionResponse(models=models, modes=mode_state)
421
+ return LoadSessionResponse(models=models, modes=mode_state, config_options=config_opts)
374
422
 
375
423
  except Exception:
376
424
  logger.exception("Failed to load session", session_id=params.session_id)
@@ -629,14 +677,8 @@ class AgentPoolACPAgent(ACPAgent):
629
677
  ) -> SetSessionModeResponse | None:
630
678
  """Set the session mode (change tool confirmation level).
631
679
 
632
- Maps ACP mode IDs to ToolConfirmationMode and calls set_tool_confirmation_mode
633
- on the session's agent. Each agent type handles the mode change appropriately:
634
- - Agent/AGUIAgent: Updates local confirmation mode
635
- - ACPAgent: Updates local mode AND forwards to remote ACP server
636
-
637
- Mode mappings:
638
- - "default": per_tool (confirm tools marked as requiring it)
639
- - "acceptEdits": never (auto-approve all tool calls)
680
+ Calls set_mode directly on the agent with the mode_id, allowing the agent
681
+ to handle mode-specific logic (e.g., acceptEdits auto-allowing edit tools).
640
682
  """
641
683
  from agentpool.agents.acp_agent import ACPAgent as ACPAgentClient
642
684
 
@@ -646,15 +688,8 @@ class AgentPoolACPAgent(ACPAgent):
646
688
  logger.warning("Session not found for mode switch", session_id=params.session_id)
647
689
  return None
648
690
 
649
- # Map mode_id to confirmation mode
650
- confirmation_mode = mode_id_to_confirmation_mode(params.mode_id)
651
- if not confirmation_mode:
652
- logger.error("Invalid mode_id", mode_id=params.mode_id)
653
- return None
654
-
655
- # All agent types support set_tool_confirmation_mode
656
- # ACPAgent handles forwarding to remote server internally
657
- await session.agent.set_tool_confirmation_mode(confirmation_mode)
691
+ # Call set_mode directly - agent handles mode-specific logic
692
+ await session.agent.set_mode(params.mode_id)
658
693
 
659
694
  # Update stored mode state for ACPAgent
660
695
  if (
@@ -665,9 +700,8 @@ class AgentPoolACPAgent(ACPAgent):
665
700
  session.agent._state.modes.current_mode_id = params.mode_id
666
701
 
667
702
  logger.info(
668
- "Set tool confirmation mode",
703
+ "Set mode",
669
704
  mode_id=params.mode_id,
670
- confirmation_mode=confirmation_mode,
671
705
  session_id=params.session_id,
672
706
  agent_type=type(session.agent).__name__,
673
707
  )
@@ -677,19 +711,15 @@ class AgentPoolACPAgent(ACPAgent):
677
711
  logger.exception("Failed to set session mode", session_id=params.session_id)
678
712
  return None
679
713
 
680
- async def set_session_model( # noqa: PLR0911
714
+ async def set_session_model(
681
715
  self, params: SetSessionModelRequest
682
716
  ) -> SetSessionModelResponse | None:
683
717
  """Set the session model.
684
718
 
685
719
  Changes the model for the active agent in the session.
686
- Validates that the requested model is in the available models list:
687
- - For ClaudeCodeAgent: validates against Claude Code models (sonnet, opus, haiku)
688
- - For native Agent: validates against server's available_models
689
- - For ACPAgent: validates against the nested agent's model list
720
+ Validates that the requested model is available via agent.get_available_models().
690
721
  """
691
722
  from agentpool.agents.acp_agent import ACPAgent as ACPAgentClient
692
- from agentpool.agents.claude_code_agent import ClaudeCodeAgent
693
723
 
694
724
  try:
695
725
  session = self.session_manager.get_session(params.session_id)
@@ -698,23 +728,8 @@ class AgentPoolACPAgent(ACPAgent):
698
728
  logger.warning(msg, session_id=params.session_id)
699
729
  return None
700
730
 
701
- # Validate model based on agent type
702
- if isinstance(session.agent, ClaudeCodeAgent):
703
- # For ClaudeCodeAgent, validate against our hardcoded models
704
- available_ids = [m.model_id for m in CLAUDE_CODE_MODELS]
705
- if params.model_id not in available_ids:
706
- logger.warning(
707
- "Model not in Claude Code available models",
708
- model_id=params.model_id,
709
- available=available_ids,
710
- )
711
- return None
712
- await session.agent.set_model(params.model_id)
713
- logger.info("Set model", model_id=params.model_id, session_id=params.session_id)
714
- return SetSessionModelResponse()
715
-
731
+ # Handle ACPAgent specially - pass through to nested agent's model state
716
732
  if isinstance(session.agent, ACPAgentClient):
717
- # For ACPAgent, validate against nested agent's model list
718
733
  if session.agent._state and session.agent._state.models:
719
734
  available_ids = [
720
735
  m.model_id for m in session.agent._state.models.available_models
@@ -727,28 +742,66 @@ class AgentPoolACPAgent(ACPAgent):
727
742
  )
728
743
  return None
729
744
  # TODO: Use ACP protocol once set_session_model stabilizes
730
- # For now, we can't actually change the model on ACPAgent
731
745
  logger.warning(
732
746
  "Model change for ACPAgent not yet supported (ACP protocol UNSTABLE)",
733
747
  model_id=params.model_id,
734
748
  )
735
749
  return None
736
750
 
737
- if isinstance(session.agent, Agent):
738
- # For native Agent, validate against server's available models
739
- available_ids = [m.model_id for m in self.available_models]
751
+ # Get available models from agent and validate
752
+ toko_models = await session.agent.get_available_models()
753
+ if toko_models:
754
+ # Build list of valid model IDs (using id_override if set)
755
+ available_ids = [m.id_override if m.id_override else m.id for m in toko_models]
740
756
  if params.model_id not in available_ids:
741
- msg = "Model not in available models"
742
- logger.warning(msg, model_id=params.model_id, available=available_ids)
757
+ logger.warning(
758
+ "Model not in available models",
759
+ model_id=params.model_id,
760
+ available=available_ids,
761
+ )
743
762
  return None
744
- session.agent.set_model(params.model_id)
745
763
 
764
+ # Set the model on the agent (all agents now have async set_model)
765
+ await session.agent.set_model(params.model_id)
746
766
  logger.info("Set model", model_id=params.model_id, session_id=params.session_id)
747
767
  return SetSessionModelResponse()
748
768
  except Exception:
749
769
  logger.exception("Failed to set session model", session_id=params.session_id)
750
770
  return None
751
771
 
772
+ async def set_session_config_option(
773
+ self, params: SetSessionConfigOptionRequest
774
+ ) -> SetSessionConfigOptionResponse | None:
775
+ """Set a session config option.
776
+
777
+ Forwards the config option change to the agent's set_mode method
778
+ and returns the updated config options.
779
+ """
780
+ try:
781
+ session = self.session_manager.get_session(params.session_id)
782
+ if not session or not session.agent:
783
+ msg = "Session not found for config option change"
784
+ logger.warning(msg, session_id=params.session_id)
785
+ return None
786
+
787
+ logger.info(
788
+ "Set config option",
789
+ config_id=params.config_id,
790
+ value=params.value,
791
+ session_id=params.session_id,
792
+ )
793
+
794
+ # Forward to agent's set_mode method
795
+ # config_id maps to category_id, value maps to mode_id
796
+ await session.agent.set_mode(params.value, category_id=params.config_id)
797
+
798
+ # Return updated config options
799
+ config_options = await get_session_config_options(session.agent)
800
+ return SetSessionConfigOptionResponse(config_options=config_options)
801
+ except Exception:
802
+ logger.exception("Failed to set session config option", session_id=params.session_id)
803
+ return None
804
+
752
805
  async def swap_pool(self, config_path: str, agent: str | None = None) -> list[str]:
753
806
  """Swap the agent pool with a new one from configuration.
754
807