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,307 @@
1
+ from __future__ import annotations, annotations as _annotations
2
+
3
+ from contextlib import asynccontextmanager
4
+ from dataclasses import dataclass
5
+ from functools import partial
6
+ from typing import TYPE_CHECKING, Any, TypeVar, assert_never
7
+ import uuid
8
+
9
+ from fasta2a.applications import FastA2A # type: ignore[import-untyped]
10
+ from fasta2a.broker import InMemoryBroker # type: ignore[import-untyped]
11
+ from fasta2a.schema import ( # type: ignore[import-untyped]
12
+ Artifact,
13
+ DataPart,
14
+ Message,
15
+ TextPart as A2ATextPart,
16
+ )
17
+ from fasta2a.storage import InMemoryStorage # type: ignore[import-untyped]
18
+ from fasta2a.worker import Worker # type: ignore[import-untyped]
19
+ from pydantic import TypeAdapter
20
+ from pydantic_ai import (
21
+ AudioUrl,
22
+ BinaryContent,
23
+ DocumentUrl,
24
+ ImageUrl,
25
+ ModelMessage,
26
+ ModelRequest,
27
+ ModelResponse,
28
+ TextPart,
29
+ ThinkingPart,
30
+ ToolCallPart,
31
+ UserPromptPart,
32
+ VideoUrl,
33
+ )
34
+
35
+
36
+ if TYPE_CHECKING:
37
+ from collections.abc import AsyncIterator, Sequence
38
+
39
+ from fasta2a.broker import Broker
40
+ from fasta2a.schema import AgentProvider, Part, Skill, TaskIdParams, TaskSendParams
41
+ from fasta2a.storage import Storage
42
+ from pydantic_ai import ModelRequestPart, ModelResponsePart, UserContent
43
+ from starlette.middleware import Middleware
44
+ from starlette.routing import Route
45
+ from starlette.types import ExceptionHandler, Lifespan
46
+
47
+ from agentpool.agents.base_agent import BaseAgent
48
+
49
+
50
+ # AgentWorker output type needs to be invariant for use in both parameter and return positions
51
+ WorkerOutputT = TypeVar("WorkerOutputT")
52
+
53
+
54
+ @asynccontextmanager
55
+ async def worker_lifespan(app: FastA2A, worker: Worker, agent: BaseAgent) -> AsyncIterator[None]:
56
+ """Custom lifespan that runs the worker during application startup.
57
+
58
+ This ensures the worker is started and ready to process tasks as soon as the application starts.
59
+ """
60
+ async with app.task_manager, agent, worker.run():
61
+ yield
62
+
63
+
64
+ def agent_to_a2a(
65
+ agent: BaseAgent,
66
+ *,
67
+ storage: Storage | None = None,
68
+ broker: Broker | None = None,
69
+ # Agent card
70
+ name: str | None = None,
71
+ url: str = "http://localhost:8000",
72
+ version: str = "1.0.0",
73
+ description: str | None = None,
74
+ provider: AgentProvider | None = None,
75
+ skills: list[Skill] | None = None,
76
+ # Starlette
77
+ debug: bool = False,
78
+ routes: Sequence[Route] | None = None,
79
+ middleware: Sequence[Middleware] | None = None,
80
+ exception_handlers: dict[Any, ExceptionHandler] | None = None,
81
+ lifespan: Lifespan[FastA2A] | None = None,
82
+ ) -> FastA2A:
83
+ """Create a FastA2A server from an agent."""
84
+ storage = storage or InMemoryStorage()
85
+ broker = broker or InMemoryBroker()
86
+ worker = AgentWorker(agent=agent, broker=broker, storage=storage)
87
+ lifespan = lifespan or partial(worker_lifespan, worker=worker, agent=agent)
88
+ return FastA2A(
89
+ storage=storage,
90
+ broker=broker,
91
+ name=name or agent.name,
92
+ url=url,
93
+ version=version,
94
+ description=description,
95
+ provider=provider,
96
+ skills=skills,
97
+ debug=debug,
98
+ routes=routes,
99
+ middleware=middleware,
100
+ exception_handlers=exception_handlers,
101
+ lifespan=lifespan,
102
+ )
103
+
104
+
105
+ @dataclass
106
+ class AgentWorker[WorkerOutputT, AgentDepsT](Worker[list[ModelMessage]]): # type: ignore[misc]
107
+ """A worker that uses an agent to execute tasks."""
108
+
109
+ agent: BaseAgent[AgentDepsT, WorkerOutputT]
110
+
111
+ async def run_task(self, params: TaskSendParams) -> None:
112
+ task = await self.storage.load_task(params["id"])
113
+ if task is None:
114
+ raise ValueError(f"Task {params['id']} not found") # pragma: no cover
115
+
116
+ # TODO(Marcelo): Should we lock `run_task` on the `context_id`?
117
+ # Ensure this task hasn't been run before
118
+ if task["status"]["state"] != "submitted":
119
+ raise ValueError( # pragma: no cover
120
+ f"Task {params['id']} has already been processed (state: {task['status']['state']})"
121
+ )
122
+
123
+ await self.storage.update_task(task["id"], state="working")
124
+
125
+ # Load context - contains pydantic-ai msg history from previous tasks in this conversation
126
+ message_history = await self.storage.load_context(task["context_id"]) or []
127
+ message_history.extend(self.build_message_history(task.get("history", [])))
128
+
129
+ try:
130
+ result = await self.agent.run(message_history=message_history) # type: ignore
131
+ messages = [
132
+ msg
133
+ for chat_msg in self.agent.conversation.chat_messages
134
+ for msg in chat_msg.messages
135
+ ]
136
+ await self.storage.update_context(task["context_id"], messages)
137
+ # Convert new messages to A2A format for task history
138
+ a2a_messages: list[Message] = []
139
+ for message in result.messages:
140
+ if isinstance(message, ModelRequest):
141
+ # Skip user prompts - they're already in task history
142
+ continue
143
+ # Convert response parts to A2A format
144
+ a2a_parts = self._response_parts_to_a2a(message.parts)
145
+ if a2a_parts: # Add if there are visible parts (text/thinking)
146
+ a2a_messages.append(
147
+ Message(
148
+ role="agent",
149
+ parts=a2a_parts,
150
+ kind="message",
151
+ message_id=str(uuid.uuid4()),
152
+ )
153
+ )
154
+
155
+ artifacts = self.build_artifacts(result.data)
156
+ except Exception:
157
+ await self.storage.update_task(task["id"], state="failed")
158
+ raise
159
+ else:
160
+ await self.storage.update_task(
161
+ task["id"], state="completed", new_artifacts=artifacts, new_messages=a2a_messages
162
+ )
163
+
164
+ async def cancel_task(self, params: TaskIdParams) -> None:
165
+ pass
166
+
167
+ def build_artifacts(self, result: WorkerOutputT) -> list[Artifact]:
168
+ """Build artifacts from agent result.
169
+
170
+ All agent outputs become artifacts to mark them as durable task outputs.
171
+ For string results, we use TextPart. For structured data, we use DataPart.
172
+ Metadata is included to preserve type information.
173
+ """
174
+ artifact_id = str(uuid.uuid4())
175
+ part = self._convert_result_to_part(result)
176
+ return [Artifact(artifact_id=artifact_id, name="result", parts=[part])]
177
+
178
+ def _convert_result_to_part(self, result: WorkerOutputT) -> Part:
179
+ """Convert agent result to a Part (TextPart or DataPart).
180
+
181
+ For string results, returns a TextPart.
182
+ For structured data, returns a DataPart with properly serialized data.
183
+ """
184
+ if isinstance(result, str):
185
+ return A2ATextPart(kind="text", text=result)
186
+ output_type = type(result)
187
+ type_adapter = TypeAdapter(output_type)
188
+ data = type_adapter.dump_python(result, mode="json")
189
+ json_schema = type_adapter.json_schema(mode="serialization")
190
+ return DataPart(kind="data", data={"result": data}, metadata={"json_schema": json_schema})
191
+
192
+ def build_message_history(self, history: list[Message]) -> list[ModelMessage]:
193
+ model_messages: list[ModelMessage] = []
194
+ for message in history:
195
+ if message["role"] == "user":
196
+ model_messages.append(
197
+ ModelRequest(parts=self._request_parts_from_a2a(message["parts"]))
198
+ )
199
+ else:
200
+ model_messages.append(
201
+ ModelResponse(parts=self._response_parts_from_a2a(message["parts"]))
202
+ )
203
+ return model_messages
204
+
205
+ def _request_parts_from_a2a(self, parts: list[Part]) -> list[ModelRequestPart]:
206
+ """Convert A2A Part objects to pydantic-ai ModelRequestPart objects.
207
+
208
+ This handles the conversion from A2A protocol parts (text, file, data) to
209
+ pydantic-ai's internal request parts (UserPromptPart with various content types).
210
+
211
+ Args:
212
+ parts: List of A2A Part objects from incoming messages
213
+
214
+ Returns:
215
+ List of ModelRequestPart objects for the pydantic-ai agent
216
+ """
217
+ model_parts: list[ModelRequestPart] = []
218
+ for part in parts:
219
+ if part["kind"] == "text":
220
+ model_parts.append(UserPromptPart(content=part["text"]))
221
+ elif part["kind"] == "file":
222
+ file_content = part["file"]
223
+ if "bytes" in file_content:
224
+ data = file_content["bytes"].encode("utf-8")
225
+ mime_type = file_content.get("mime_type", "application/octet-stream")
226
+ content: UserContent = BinaryContent(data=data, media_type=mime_type)
227
+ model_parts.append(UserPromptPart(content=[content]))
228
+ else:
229
+ url = file_content["uri"]
230
+ for url_cls in (DocumentUrl, AudioUrl, ImageUrl, VideoUrl):
231
+ content = url_cls(url=url)
232
+ try:
233
+ content.media_type # noqa: B018
234
+ except ValueError: # pragma: no cover
235
+ continue
236
+ else:
237
+ break
238
+ else:
239
+ raise ValueError(f"Unsupported file type: {url}") # pragma: no cover
240
+ model_parts.append(UserPromptPart(content=[content]))
241
+ elif part["kind"] == "data":
242
+ raise NotImplementedError("Data parts are not supported yet.")
243
+ else:
244
+ assert_never(part)
245
+ return model_parts
246
+
247
+ def _response_parts_from_a2a(self, parts: list[Part]) -> list[ModelResponsePart]:
248
+ """Convert A2A Part objects to pydantic-ai ModelResponsePart objects.
249
+
250
+ This handles the conversion from A2A protocol parts (text, file, data) to
251
+ pydantic-ai's internal response parts. Currently only supports text parts
252
+ as agent responses in A2A are expected to be text-based.
253
+
254
+ Args:
255
+ parts: List of A2A Part objects from stored agent messages
256
+
257
+ Returns:
258
+ List of ModelResponsePart objects for message history
259
+ """
260
+ model_parts: list[ModelResponsePart] = []
261
+ for part in parts:
262
+ if part["kind"] == "text":
263
+ model_parts.append(TextPart(content=part["text"]))
264
+ elif part["kind"] == "file": # pragma: no cover
265
+ raise NotImplementedError("File parts are not supported yet.")
266
+ elif part["kind"] == "data": # pragma: no cover
267
+ raise NotImplementedError("Data parts are not supported yet.")
268
+ else: # pragma: no cover
269
+ assert_never(part)
270
+ return model_parts
271
+
272
+ def _response_parts_to_a2a(self, parts: Sequence[ModelResponsePart]) -> list[Part]:
273
+ """Convert pydantic-ai ModelResponsePart objects to A2A Part objects.
274
+
275
+ This handles the conversion from pydantic-ai's internal response parts to
276
+ A2A protocol parts. Different part types are handled as follows:
277
+ - TextPart: Converted directly to A2A TextPart
278
+ - ThinkingPart: Converted to TextPart with metadata indicating it's thinking
279
+ - ToolCallPart: Skipped (internal to agent execution)
280
+
281
+ Args:
282
+ parts: List of ModelResponsePart objects from agent response
283
+
284
+ Returns:
285
+ List of A2A Part objects suitable for sending via A2A protocol
286
+ """
287
+ a2a_parts: list[Part] = []
288
+ for part in parts:
289
+ if isinstance(part, TextPart):
290
+ a2a_parts.append(A2ATextPart(kind="text", text=part.content))
291
+ elif isinstance(part, ThinkingPart):
292
+ # Convert thinking to text with metadata
293
+ a2a_parts.append(
294
+ A2ATextPart(
295
+ kind="text",
296
+ text=part.content,
297
+ metadata={
298
+ "type": "thinking",
299
+ "thinking_id": part.id,
300
+ "signature": part.signature,
301
+ },
302
+ )
303
+ )
304
+ elif isinstance(part, ToolCallPart):
305
+ # Skip tool calls - they're internal to agent execution
306
+ pass
307
+ return a2a_parts
@@ -72,8 +72,16 @@ class A2AServer(HTTPServer):
72
72
  Returns:
73
73
  List of Route objects for each agent plus root listing endpoint
74
74
  """
75
+ from functools import partial
76
+
77
+ from fasta2a import FastA2A # type: ignore[import-untyped]
78
+ from fasta2a.broker import InMemoryBroker # type: ignore[import-untyped]
79
+ from fasta2a.storage import InMemoryStorage # type: ignore[import-untyped]
80
+ from starlette.responses import JSONResponse, Response
75
81
  from starlette.routing import Route
76
82
 
83
+ from agentpool_server.a2a_server.agent_worker import AgentWorker, worker_lifespan
84
+
77
85
  routes: list[Route] = []
78
86
 
79
87
  # Create route for each agent in the pool
@@ -81,33 +89,30 @@ class A2AServer(HTTPServer):
81
89
 
82
90
  async def agent_handler(request: Request, agent_name: str = agent_name) -> Response:
83
91
  """Handle A2A requests for a specific agent."""
84
- from starlette.responses import JSONResponse
85
-
86
92
  try:
87
93
  # Get the agent from pool
88
- pool_agent = self.pool.agents.get(agent_name)
89
- if pool_agent is None:
90
- return JSONResponse(
91
- {"error": f"Agent '{agent_name}' not found"},
92
- status_code=404,
93
- )
94
-
94
+ agent = self.pool.agents.get(agent_name)
95
+ if agent is None:
96
+ error = {"error": f"Agent '{agent_name}' not found"}
97
+ return JSONResponse(error, status_code=404)
95
98
  # Get the underlying pydantic-ai agentlet and convert to A2A app
96
- agentlet = await pool_agent.get_agentlet(None, pool_agent.model_name, str)
97
- a2a_app = agentlet.to_a2a()
98
-
99
+ storage = InMemoryStorage()
100
+ broker = InMemoryBroker()
101
+ worker = AgentWorker(agent=agent, broker=broker, storage=storage)
102
+ lifespan = partial(worker_lifespan, worker=worker, agent=agent)
103
+ a2a_app = FastA2A(
104
+ storage=storage,
105
+ broker=broker,
106
+ name=agent.name,
107
+ lifespan=lifespan,
108
+ )
99
109
  # ASGI apps don't return a value, they write to send()
100
110
  await a2a_app(request.scope, request.receive, request._send)
101
- from starlette.responses import Response
102
-
103
111
  return Response()
104
112
 
105
113
  except Exception as e:
106
114
  self.log.exception("Error handling A2A request", agent=agent_name)
107
- return JSONResponse(
108
- {"error": str(e)},
109
- status_code=500,
110
- )
115
+ return JSONResponse({"error": str(e)}, status_code=500)
111
116
 
112
117
  # A2A protocol routes
113
118
  routes.append(Route(f"/{agent_name}", agent_handler, methods=["POST"]))