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
@@ -5,9 +5,8 @@ from __future__ import annotations
5
5
  from abc import ABC, abstractmethod
6
6
  from collections.abc import Sequence
7
7
  from typing import TYPE_CHECKING, Any, Literal, Self, overload
8
- from uuid import uuid4
9
8
 
10
- from psygnal import Signal
9
+ from anyenv.signals import Signal
11
10
 
12
11
  from agentpool.log import get_logger
13
12
  from agentpool.messaging import ChatMessage
@@ -20,8 +19,8 @@ if TYPE_CHECKING:
20
19
  from datetime import timedelta
21
20
  from types import TracebackType
22
21
 
23
- from evented.configs import EventConfig
24
22
  from evented.event_data import EventData
23
+ from evented_config import EventConfig
25
24
 
26
25
  from agentpool.common_types import (
27
26
  AnyTransformFn,
@@ -34,7 +33,7 @@ if TYPE_CHECKING:
34
33
  from agentpool.storage import StorageManager
35
34
  from agentpool.talk import Talk, TeamTalk
36
35
  from agentpool.talk.stats import AggregatedMessageStats, MessageStats
37
- from agentpool.tools.base import Tool
36
+ from agentpool.tools.base import FunctionTool
38
37
  from agentpool_config.forward_targets import ConnectionType
39
38
  from agentpool_config.mcp_server import MCPServerConfig
40
39
 
@@ -45,10 +44,10 @@ logger = get_logger(__name__)
45
44
  class MessageNode[TDeps, TResult](ABC):
46
45
  """Base class for all message processing nodes."""
47
46
 
48
- message_received = Signal(ChatMessage)
47
+ message_received = Signal[ChatMessage[Any]]()
49
48
  """Signal emitted when node receives a message."""
50
49
 
51
- message_sent = Signal(ChatMessage)
50
+ message_sent = Signal[ChatMessage[Any]]()
52
51
  """Signal emitted when node creates a message."""
53
52
 
54
53
  def __init__(
@@ -86,18 +85,33 @@ class MessageNode[TDeps, TResult](ABC):
86
85
  name_ = f"node_{self._name}"
87
86
  self.mcp = MCPManager(name_, servers=mcp_servers, owner=self.name)
88
87
  self.enable_db_logging = enable_logging
89
- self.conversation_id = str(uuid4())
90
- # Connect to the combined signal to capture all messages
91
- # TODO: need to check this
92
- # node.message_received.connect(self.log_message)
88
+ self.conversation_id: str | None = None
89
+ self.conversation_title: str | None = None
93
90
 
94
- async def __aenter__(self) -> Self:
95
- """Initialize base message node."""
96
- if self.enable_db_logging and self.storage:
91
+ def _set_conversation_title(self, title: str) -> None:
92
+ """Callback for setting conversation title (called by storage manager)."""
93
+ self.conversation_title = title
94
+
95
+ async def log_conversation(self, initial_prompt: str | None = None) -> None:
96
+ """Log conversation to storage if enabled.
97
+
98
+ Should be called at the start of run_stream() after conversation_id is set.
99
+ For native agents, generate conversation_id first with uuid4().
100
+ For wrapped agents (Claude Code), set conversation_id from SDK session first.
101
+
102
+ Args:
103
+ initial_prompt: Optional initial prompt to trigger title generation.
104
+ """
105
+ if self.enable_db_logging and self.storage and self.conversation_id:
97
106
  await self.storage.log_conversation(
98
107
  conversation_id=self.conversation_id,
99
108
  node_name=self.name,
109
+ initial_prompt=initial_prompt,
110
+ on_title_generated=self._set_conversation_title,
100
111
  )
112
+
113
+ async def __aenter__(self) -> Self:
114
+ """Initialize base message node."""
101
115
  try:
102
116
  await self._events.__aenter__()
103
117
  await self.mcp.__aenter__()
@@ -161,7 +175,7 @@ class MessageNode[TDeps, TResult](ABC):
161
175
 
162
176
  def to_tool(
163
177
  self, *, name: str | None = None, description: str | None = None, **kwargs: Any
164
- ) -> Tool[TResult]:
178
+ ) -> FunctionTool[TResult]:
165
179
  """Convert node to a callable tool.
166
180
 
167
181
  Args:
@@ -354,6 +368,30 @@ class MessageNode[TDeps, TResult](ABC):
354
368
  async def run(self, *prompts: Any, **kwargs: Any) -> ChatMessage[TResult]:
355
369
  """Execute node with prompts. Implementation-specific run logic."""
356
370
 
371
+ async def run_message(
372
+ self,
373
+ message: ChatMessage[Any],
374
+ **kwargs: Any,
375
+ ) -> ChatMessage[TResult]:
376
+ """Run with an incoming ChatMessage (e.g., from Talk routing).
377
+
378
+ Extracts content from the message, preserves conversation_id,
379
+ and sets parent_id to track the message chain.
380
+
381
+ Args:
382
+ message: The incoming ChatMessage to process
383
+ **kwargs: Additional arguments passed to run()
384
+
385
+ Returns:
386
+ Response ChatMessage with message chain tracked via parent_id
387
+ """
388
+ return await self.run(
389
+ message.content,
390
+ conversation_id=message.conversation_id,
391
+ parent_id=message.message_id,
392
+ **kwargs,
393
+ )
394
+
357
395
  async def get_message_history(self, limit: int | None = None) -> list[ChatMessage[Any]]:
358
396
  """Get message history from storage."""
359
397
  from agentpool_config.session import SessionQuery
@@ -30,6 +30,7 @@ from agentpool.common_types import MessageRole, SimpleJsonType # noqa: TC001
30
30
  from agentpool.log import get_logger
31
31
  from agentpool.utils.inspection import dataclasses_no_defaults_repr
32
32
  from agentpool.utils.now import get_now
33
+ from agentpool.utils.pydantic_ai_helpers import safe_args_as_dict
33
34
 
34
35
 
35
36
  if TYPE_CHECKING:
@@ -71,9 +72,7 @@ Metadata:
71
72
  {{ key }}: {{ value }}
72
73
  {%- endfor %}
73
74
  {%- endif %}
74
- {%- if forwarded_from %}
75
- Forwarded via: {{ forwarded_from|join(' -> ') }}
76
- {%- endif %}"""
75
+ """
77
76
 
78
77
  MARKDOWN_TEMPLATE = """## {{ name or role.title() }}
79
78
  *{{ timestamp.strftime('%Y-%m-%d %H:%M:%S') }}*
@@ -97,10 +96,7 @@ MARKDOWN_TEMPLATE = """## {{ name or role.title() }}
97
96
  ```
98
97
  {%- endif %}
99
98
 
100
- {% if forwarded_from %}
101
-
102
- *Forwarded via: {{ forwarded_from|join(' → ') }}*
103
- {% endif %}"""
99
+ """
104
100
 
105
101
  MESSAGE_TEMPLATES = {
106
102
  "simple": SIMPLE_TEMPLATE,
@@ -198,6 +194,14 @@ class ChatMessage[TContent]:
198
194
  conversation_id: str | None = None
199
195
  """ID of the conversation this message belongs to."""
200
196
 
197
+ parent_id: str | None = None
198
+ """ID of the parent message for tree-structured conversations.
199
+
200
+ This enables branching conversations where each message knows its predecessor.
201
+ For user messages, this typically points to the previous assistant response.
202
+ For assistant responses, this points to the user message being responded to.
203
+ """
204
+
201
205
  response_time: float | None = None
202
206
  """Time it took the LLM to respond."""
203
207
 
@@ -207,9 +211,6 @@ class ChatMessage[TContent]:
207
211
  name: str | None = None
208
212
  """Display name for the message sender in UI."""
209
213
 
210
- forwarded_from: list[str] = field(default_factory=list)
211
- """List of agent names (the chain) that forwarded this message to the sender."""
212
-
213
214
  provider_details: dict[str, Any] = field(default_factory=dict)
214
215
  """Provider specific metadata / extra information."""
215
216
 
@@ -295,12 +296,29 @@ class ChatMessage[TContent]:
295
296
  message: TPromptContent,
296
297
  conversation_id: str | None = None,
297
298
  instructions: str | None = None,
299
+ parent_id: str | None = None,
298
300
  ) -> ChatMessage[TPromptContent]:
299
- """Create a user prompt message."""
301
+ """Create a user prompt message.
302
+
303
+ Args:
304
+ message: The prompt content
305
+ conversation_id: ID of the conversation
306
+ instructions: Optional instructions for the model
307
+ parent_id: ID of the parent message (typically the previous assistant response)
308
+
309
+ Returns:
310
+ A ChatMessage representing the user prompt
311
+ """
300
312
  part = UserPromptPart(content=message)
301
313
  request = ModelRequest(parts=[part], instructions=instructions)
302
314
  id_ = conversation_id or str(uuid4())
303
- return ChatMessage(messages=[request], role="user", content=message, conversation_id=id_)
315
+ return ChatMessage(
316
+ messages=[request],
317
+ role="user",
318
+ content=message,
319
+ conversation_id=id_,
320
+ parent_id=parent_id,
321
+ )
304
322
 
305
323
  @classmethod
306
324
  def from_pydantic_ai[TContentType](
@@ -309,7 +327,7 @@ class ChatMessage[TContent]:
309
327
  message: ModelMessage,
310
328
  conversation_id: str | None = None,
311
329
  name: str | None = None,
312
- forwarded_from: list[str] | None = None,
330
+ parent_id: str | None = None,
313
331
  ) -> ChatMessage[TContentType]:
314
332
  """Convert a Pydantic model to a ChatMessage."""
315
333
  match message:
@@ -319,9 +337,8 @@ class ChatMessage[TContent]:
319
337
  content=content,
320
338
  role="user",
321
339
  message_id=run_id or str(uuid.uuid4()),
322
- # instructions=instructions,
323
- forwarded_from=forwarded_from or [],
324
340
  name=name,
341
+ parent_id=parent_id,
325
342
  )
326
343
  case ModelResponse(
327
344
  usage=usage,
@@ -347,7 +364,7 @@ class ChatMessage[TContent]:
347
364
  finish_reason=finish_reason,
348
365
  provider_response_id=provider_response_id,
349
366
  name=name,
350
- forwarded_from=forwarded_from or [],
367
+ parent_id=parent_id,
351
368
  )
352
369
  case _ as unreachable:
353
370
  assert_never(unreachable)
@@ -360,7 +377,9 @@ class ChatMessage[TContent]:
360
377
  agent_name: str | None = None,
361
378
  message_id: str | None = None,
362
379
  conversation_id: str | None = None,
380
+ parent_id: str | None = None,
363
381
  response_time: float,
382
+ metadata: SimpleJsonType | None = None,
364
383
  ) -> ChatMessage[OutputDataT]:
365
384
  """Create a ChatMessage from a PydanticAI run result.
366
385
 
@@ -369,19 +388,29 @@ class ChatMessage[TContent]:
369
388
  agent_name: Name of the agent that generated this response
370
389
  message_id: Unique message identifier
371
390
  conversation_id: Conversation identifier
391
+ parent_id: ID of the parent message (typically the user message)
372
392
  response_time: Total time taken for the response
393
+ metadata: Optional metadata to attach to the message
373
394
 
374
395
  Returns:
375
396
  A ChatMessage with all fields populated from the result
376
397
  """
377
- # Calculate costs
398
+ # Calculate costs - prefer provider-reported cost if available
378
399
  run_usage = result.usage()
379
400
  usage = result.response.usage
380
- cost_info = await TokenCost.from_usage(
381
- model=result.response.model_name or "",
382
- usage=run_usage,
383
- provider=result.response.provider_name,
384
- )
401
+ provider_cost = (result.response.provider_details or {}).get("cost")
402
+ if provider_cost is not None:
403
+ # Use actual cost from provider (e.g., OpenRouter returns this)
404
+ cost_info: TokenCost | None = TokenCost(
405
+ token_usage=run_usage, total_cost=Decimal(str(provider_cost))
406
+ )
407
+ else:
408
+ # Fall back to calculated cost
409
+ cost_info = await TokenCost.from_usage(
410
+ model=result.response.model_name or "",
411
+ usage=run_usage,
412
+ provider=result.response.provider_name,
413
+ )
385
414
 
386
415
  return ChatMessage[OutputDataT](
387
416
  content=result.output,
@@ -395,23 +424,13 @@ class ChatMessage[TContent]:
395
424
  provider_name=result.response.provider_name,
396
425
  message_id=message_id or str(uuid4()),
397
426
  conversation_id=conversation_id,
427
+ parent_id=parent_id,
398
428
  cost_info=cost_info,
399
429
  response_time=response_time,
400
430
  provider_details={},
431
+ metadata=metadata or {},
401
432
  )
402
433
 
403
- def forwarded(self, previous_message: ChatMessage[Any]) -> Self:
404
- """Create new message showing it was forwarded from another message.
405
-
406
- Args:
407
- previous_message: The message that led to this one's creation
408
-
409
- Returns:
410
- New message with updated chain showing the path through previous message
411
- """
412
- from_ = [*previous_message.forwarded_from, previous_message.name or "unknown"]
413
- return replace(self, forwarded_from=from_)
414
-
415
434
  @property
416
435
  def response(self) -> ModelResponse:
417
436
  """Return the last response from the message history."""
@@ -509,7 +528,7 @@ class ChatMessage[TContent]:
509
528
  call_part = call_parts[part.tool_call_id]
510
529
  tool_info = ToolCallInfo(
511
530
  tool_name=call_part.tool_name,
512
- args=call_part.args_as_dict(),
531
+ args=safe_args_as_dict(call_part),
513
532
  agent_name=agent_name or "UNSET",
514
533
  result=part.content,
515
534
  tool_call_id=call_part.tool_call_id or str(uuid4()),
@@ -17,32 +17,27 @@ if TYPE_CHECKING:
17
17
 
18
18
 
19
19
  async def prepare_prompts(
20
- *prompt: PromptCompatible | ChatMessage[Any],
21
- ) -> tuple[ChatMessage[Any], list[UserContent], ChatMessage[Any] | None]:
20
+ *prompt: PromptCompatible,
21
+ parent_id: str | None = None,
22
+ conversation_id: str | None = None,
23
+ ) -> tuple[ChatMessage[Any], list[UserContent]]:
22
24
  """Prepare prompts for processing.
23
25
 
24
- Extracted from MessageNode.pre_run logic.
25
-
26
26
  Args:
27
27
  *prompt: The prompt(s) to prepare.
28
+ parent_id: Optional ID of the parent message (typically the previous response).
29
+ conversation_id: Optional conversation ID for the user message.
28
30
 
29
31
  Returns:
30
32
  A tuple of:
31
- - Either incoming message, or a constructed incoming message based
32
- on the prompt(s).
33
+ - A ChatMessage representing the user prompt.
33
34
  - A list of prompts to be sent to the model.
34
- - The original ChatMessage if forwarded, None otherwise
35
35
  """
36
- if len(prompt) == 1 and isinstance(prompt[0], ChatMessage):
37
- original_msg = prompt[0]
38
- # Update received message's chain to show it came through its source
39
- user_msg = original_msg.forwarded(original_msg).to_request()
40
- prompts = await convert_prompts([user_msg.content])
41
- # clear cost info to avoid double-counting
42
- return user_msg, prompts, original_msg
43
36
  prompts = await convert_prompts(prompt)
44
- user_msg = ChatMessage.user_prompt(message=prompts)
45
- return user_msg, prompts, None
37
+ user_msg = ChatMessage.user_prompt(
38
+ message=prompts, parent_id=parent_id, conversation_id=conversation_id
39
+ )
40
+ return user_msg, prompts
46
41
 
47
42
 
48
43
  async def finalize_message(
@@ -50,7 +45,6 @@ async def finalize_message(
50
45
  previous_message: ChatMessage[Any] | None,
51
46
  node: MessageNode[Any, Any],
52
47
  connections: ConnectionManager,
53
- original_message: ChatMessage[Any] | None,
54
48
  wait_for_connections: bool | None = None,
55
49
  ) -> ChatMessage[Any]:
56
50
  """Handle message finalization and routing.
@@ -60,16 +54,12 @@ async def finalize_message(
60
54
  previous_message: The original user message (if any)
61
55
  node: The message node that produced the message
62
56
  connections: Connection manager for routing
63
- original_message: The original ChatMessage if forwarded, None otherwise
64
57
  wait_for_connections: Whether to wait for connected nodes
65
58
 
66
59
  Returns:
67
60
  The finalized message
68
61
  """
69
- # For chain processing, update the response's chain if input was forwarded
70
- if original_message:
71
- message = message.forwarded(original_message)
72
- node.message_sent.emit(message) # Emit signals
62
+ await node.message_sent.emit(message) # Emit signals
73
63
  await node.log_message(message) # Log message if enabled
74
64
  # Route to connections
75
65
  await connections.route_message(message, wait=wait_for_connections)
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from agentpool.models.acp_agents import ACPAgentConfig, ACPAgentConfigTypes, BaseACPAgentConfig
6
- from agentpool.models.agents import NativeAgentConfig
6
+ from agentpool.models.agents import AnyToolConfig, NativeAgentConfig # noqa: F401
7
7
  from agentpool.models.agui_agents import AGUIAgentConfig
8
8
  from agentpool.models.claude_code_agents import ClaudeCodeAgentConfig
9
9
  from agentpool.models.manifest import AgentsManifest, AnyAgentConfig
@@ -7,13 +7,12 @@ import os
7
7
  import tempfile
8
8
  from typing import TYPE_CHECKING, Annotated, Any, Literal
9
9
 
10
- from exxec import ExecutionEnvironmentStr, get_environment # noqa: TC002
11
- from exxec.configs import (
10
+ from exxec_config import (
12
11
  E2bExecutionEnvironmentConfig,
13
12
  ExecutionEnvironmentConfig, # noqa: TC002
13
+ ExecutionEnvironmentStr, # noqa: TC002
14
14
  )
15
15
  from pydantic import ConfigDict, Field
16
- from tokonomics.model_discovery import ProviderType # noqa: TC002
17
16
 
18
17
  from agentpool_config.nodes import NodeConfig
19
18
  from agentpool_config.system_prompts import PromptConfig # noqa: TC001
@@ -224,6 +223,8 @@ class BaseACPAgentConfig(NodeConfig):
224
223
 
225
224
  def get_execution_environment(self) -> ExecutionEnvironment:
226
225
  """Create execution environment from config."""
226
+ from exxec import get_environment
227
+
227
228
  if isinstance(self.execution_environment, str):
228
229
  return get_environment(self.execution_environment)
229
230
  return self.execution_environment.get_provider()
@@ -233,21 +234,14 @@ class BaseACPAgentConfig(NodeConfig):
233
234
 
234
235
  Returns None if not configured (caller should fall back to main env).
235
236
  """
237
+ from exxec import get_environment
238
+
236
239
  if self.client_execution_environment is None:
237
240
  return None
238
241
  if isinstance(self.client_execution_environment, str):
239
242
  return get_environment(self.client_execution_environment)
240
243
  return self.client_execution_environment.get_provider()
241
244
 
242
- @property
243
- def model_providers(self) -> list[ProviderType]:
244
- """Return the model providers used by this ACP agent.
245
-
246
- Override in subclasses to specify which providers the agent uses.
247
- Used for intelligent model discovery and fallback configuration.
248
- """
249
- return []
250
-
251
245
 
252
246
  class ACPAgentConfig(BaseACPAgentConfig):
253
247
  """Configuration for a custom ACP agent with explicit command.
@@ -286,18 +280,6 @@ class ACPAgentConfig(BaseACPAgentConfig):
286
280
  )
287
281
  """Arguments to pass to the command."""
288
282
 
289
- providers: list[ProviderType] = Field(
290
- default_factory=list,
291
- title="Providers",
292
- examples=[["openai", "anthropic"], ["gemini"]],
293
- )
294
- """Model providers this agent can use."""
295
-
296
- @property
297
- def model_providers(self) -> list[ProviderType]:
298
- """Return configured providers for custom ACP agents."""
299
- return list(self.providers)
300
-
301
283
  def get_command(self) -> str:
302
284
  """Get the command to spawn the ACP server."""
303
285
  return self.command