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
@@ -2,7 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Any, Self
5
+ from dataclasses import dataclass
6
+ from typing import TYPE_CHECKING, Any, Literal, Self
7
+
8
+ from anyenv.signals import Signal
6
9
 
7
10
  from agentpool.log import get_logger
8
11
  from agentpool.tools.base import Tool
@@ -10,34 +13,88 @@ from agentpool_config.tools import ToolHints
10
13
 
11
14
 
12
15
  if TYPE_CHECKING:
13
- from collections.abc import Callable
16
+ from collections.abc import Callable, Sequence
14
17
  from types import TracebackType
15
18
 
19
+ from fsspec.asyn import AsyncFileSystem
16
20
  from pydantic_ai import ModelRequestPart
17
21
  from schemez import OpenAIFunctionDefinition
18
22
 
19
23
  from agentpool.prompts.prompts import BasePrompt
24
+ from agentpool.resource_providers.resource_info import ResourceInfo
20
25
  from agentpool.skills.skill import Skill
21
26
  from agentpool.tools.base import ToolKind
22
- from agentpool_config.resources import ResourceInfo
23
27
 
24
28
 
25
29
  logger = get_logger(__name__)
26
30
 
27
31
 
32
+ ResourceType = Literal["tools", "prompts", "resources", "skills"]
33
+ ProviderKind = Literal[
34
+ "base", "mcp", "mcp_run", "tools", "prompts", "skills", "aggregating", "custom"
35
+ ]
36
+
37
+
38
+ @dataclass(frozen=True, slots=True)
39
+ class ResourceChangeEvent:
40
+ """Event emitted when resources change in a provider.
41
+
42
+ Attributes:
43
+ provider_name: Name of the provider instance
44
+ provider_kind: Kind/type of the provider (e.g., "mcp", "tools")
45
+ resource_type: Type of resource that changed
46
+ owner: Optional owner of the provider (e.g., agent name)
47
+ """
48
+
49
+ provider_name: str
50
+ provider_kind: ProviderKind
51
+ resource_type: ResourceType
52
+ owner: str | None = None
53
+
54
+
28
55
  class ResourceProvider:
29
56
  """Base class for resource providers.
30
57
 
31
58
  Provides tools, prompts, and other resources to agents.
32
59
  Default implementations return empty lists - override as needed.
60
+
61
+ Class Attributes:
62
+ kind: Short slug identifying the provider type (e.g., "mcp", "tools")
63
+
64
+ Change signals (using anyenv.signals.Signal):
65
+ - tools_changed: Emitted when tools change
66
+ - prompts_changed: Emitted when prompts change
67
+ - resources_changed: Emitted when resources change
68
+ - skills_changed: Emitted when skills change
69
+
70
+ Example:
71
+ provider.tools_changed.connect(my_handler)
72
+ await provider.tools_changed.emit(provider.create_change_event("tools"))
33
73
  """
34
74
 
75
+ kind: ProviderKind = "base"
76
+
77
+ # Change signals - emit ResourceChangeEvent when resources change
78
+ tools_changed: Signal[ResourceChangeEvent] = Signal()
79
+ prompts_changed: Signal[ResourceChangeEvent] = Signal()
80
+ resources_changed: Signal[ResourceChangeEvent] = Signal()
81
+ skills_changed: Signal[ResourceChangeEvent] = Signal()
82
+
35
83
  def __init__(self, name: str, owner: str | None = None) -> None:
36
84
  """Initialize the resource provider."""
37
85
  self.name = name
38
86
  self.owner = owner
39
87
  self.log = logger.bind(name=self.name, owner=self.owner)
40
88
 
89
+ def create_change_event(self, resource_type: ResourceType) -> ResourceChangeEvent:
90
+ """Create a ResourceChangeEvent for this provider."""
91
+ return ResourceChangeEvent(
92
+ provider_name=self.name,
93
+ provider_kind=self.kind,
94
+ resource_type=resource_type,
95
+ owner=self.owner,
96
+ )
97
+
41
98
  async def __aenter__(self) -> Self:
42
99
  """Async context entry if required."""
43
100
  return self
@@ -53,7 +110,7 @@ class ResourceProvider:
53
110
  def __repr__(self) -> str:
54
111
  return f"{self.__class__.__name__}(name={self.name!r})"
55
112
 
56
- async def get_tools(self) -> list[Tool]:
113
+ async def get_tools(self) -> Sequence[Tool]:
57
114
  """Get available tools. Override to provide tools."""
58
115
  return []
59
116
 
@@ -78,6 +135,15 @@ class ResourceProvider:
78
135
  """Get available skills. Override to provide skills."""
79
136
  return []
80
137
 
138
+ def get_fs(self) -> AsyncFileSystem | None:
139
+ """Get filesystem view of provider state/history.
140
+
141
+ Returns:
142
+ AsyncFileSystem or None if not supported.
143
+ Override to expose provider state through filesystem interface.
144
+ """
145
+ return None
146
+
81
147
  async def get_skill_instructions(self, skill_name: str) -> str:
82
148
  """Get full instructions for a specific skill.
83
149
 
@@ -5,6 +5,8 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  import contextlib
7
7
  from dataclasses import dataclass
8
+ from datetime import UTC, datetime
9
+ import json
8
10
  from typing import TYPE_CHECKING, Any, Self
9
11
 
10
12
  import anyio
@@ -18,9 +20,10 @@ if TYPE_CHECKING:
18
20
  from types import TracebackType
19
21
 
20
22
  from exxec.base import ExecutionEnvironment
21
- from exxec.configs import ExecutionEnvironmentConfig
22
23
  from exxec.models import ServerInfo
24
+ from exxec_config import ExecutionEnvironmentConfig
23
25
  from fastapi import FastAPI
26
+ from fsspec.asyn import AsyncFileSystem
24
27
  from schemez import ToolsetCodeGenerator
25
28
  import uvicorn
26
29
 
@@ -46,6 +49,14 @@ class RemoteCodeExecutor:
46
49
  execution_env: ExecutionEnvironment
47
50
  """Execution environment for running code."""
48
51
 
52
+ def __post_init__(self) -> None:
53
+ """Initialize filesystem for script history after dataclass init."""
54
+ from fsspec.implementations.asyn_wrapper import AsyncFileSystemWrapper
55
+ from fsspec.implementations.memory import MemoryFileSystem
56
+
57
+ self._memory_fs = MemoryFileSystem()
58
+ self._fs = AsyncFileSystemWrapper(self._memory_fs)
59
+
49
60
  @classmethod
50
61
  def from_tools(
51
62
  cls,
@@ -78,16 +89,70 @@ class RemoteCodeExecutor:
78
89
  """Get comprehensive description of available tools."""
79
90
  return self.toolset_generator.generate_tool_description()
80
91
 
81
- async def execute_code(self, code: str) -> Any:
92
+ def get_fs(self) -> AsyncFileSystem:
93
+ """Get filesystem view of script history.
94
+
95
+ Returns:
96
+ AsyncFileSystem containing:
97
+ - scripts/{timestamp}_{title}.py - Executed scripts
98
+ - scripts/{timestamp}_{title}.json - Execution metadata
99
+ """
100
+ return self._fs
101
+
102
+ async def execute_code(self, code: str, title: str) -> Any:
82
103
  """Execute code with tools available via HTTP API.
83
104
 
84
105
  Args:
85
106
  code: Python code to execute
107
+ title: Short descriptive title for this script (3-4 words)
86
108
 
87
109
  Returns:
88
110
  Execution result from the environment
89
111
  """
90
- return await self.execution_env.execute(code)
112
+ start_time = datetime.now(UTC)
113
+ exit_code = 0
114
+ error_msg: str | None = None
115
+ exec_result = None
116
+ result_str = ""
117
+
118
+ try:
119
+ exec_result = await self.execution_env.execute(code)
120
+ result_str = str(exec_result)
121
+ # Check if execution failed
122
+ if (
123
+ hasattr(exec_result, "exit_code")
124
+ and exec_result.exit_code is not None
125
+ and exec_result.exit_code != 0
126
+ ):
127
+ exit_code = exec_result.exit_code
128
+ error_msg = getattr(exec_result, "error", None)
129
+ except Exception as e: # noqa: BLE001
130
+ exit_code = 1
131
+ error_msg = str(e)
132
+ result_str = f"Error executing code: {error_msg}"
133
+ finally:
134
+ # Save to filesystem
135
+ end_time = datetime.now(UTC)
136
+ duration = (end_time - start_time).total_seconds()
137
+ timestamp = start_time.strftime("%Y%m%d_%H%M%S")
138
+
139
+ # Write script file
140
+ script_path = f"scripts/{timestamp}_{title}.py"
141
+ self._memory_fs.pipe(script_path, code.encode("utf-8"))
142
+
143
+ # Write metadata file
144
+ metadata = {
145
+ "title": title,
146
+ "timestamp": start_time.isoformat(),
147
+ "exit_code": exit_code,
148
+ "duration": duration,
149
+ "result": result_str,
150
+ "error": error_msg,
151
+ }
152
+ metadata_path = f"scripts/{timestamp}_{title}.json"
153
+ self._memory_fs.pipe(metadata_path, json.dumps(metadata, indent=2).encode("utf-8"))
154
+
155
+ return exec_result
91
156
 
92
157
  async def __aenter__(self) -> Self:
93
158
  """Async context manager entry."""
@@ -196,7 +261,7 @@ class ToolServerLifecycleHandler:
196
261
 
197
262
 
198
263
  if __name__ == "__main__":
199
- from exxec.configs import LocalExecutionEnvironmentConfig
264
+ from exxec_config import LocalExecutionEnvironmentConfig
200
265
 
201
266
  from agentpool.tools.base import Tool
202
267
 
@@ -209,7 +274,9 @@ if __name__ == "__main__":
209
274
  config = LocalExecutionEnvironmentConfig()
210
275
  provider = RemoteCodeExecutor.from_tools(tools, config, server_port=9876)
211
276
  async with provider:
212
- result = await provider.execute_code("_result = await add_numbers(5, 3)")
277
+ result = await provider.execute_code(
278
+ "_result = await add_numbers(5, 3)", title="test_addition"
279
+ )
213
280
  print(f"Result: {result.result}")
214
281
 
215
282
  anyio.run(main)
@@ -69,13 +69,13 @@ def tools_to_codegen(
69
69
  generators = [
70
70
  ToolCodeGenerator(
71
71
  schema=create_schema(
72
- t.callable,
72
+ t.get_callable(),
73
73
  name_override=t.name,
74
74
  description_override=t.description,
75
75
  mode="openai",
76
76
  exclude_types=[AgentContext, RunContext],
77
77
  ),
78
- callable=t.callable,
78
+ callable=t.get_callable(),
79
79
  name_override=t.name,
80
80
  )
81
81
  for t in tools
@@ -2,8 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from datetime import UTC, datetime
5
6
  from functools import partial
6
7
  import inspect
8
+ import json
7
9
  from typing import TYPE_CHECKING, Any
8
10
 
9
11
  from agentpool.agents.context import AgentContext # noqa: TC001
@@ -17,6 +19,9 @@ from agentpool_toolsets.fsspec_toolset.toolset import FSSpecTools
17
19
 
18
20
 
19
21
  if TYPE_CHECKING:
22
+ from collections.abc import Sequence
23
+
24
+ from fsspec.asyn import AsyncFileSystem
20
25
  from schemez import ToolsetCodeGenerator
21
26
 
22
27
  from agentpool.resource_providers import ResourceProvider
@@ -46,7 +51,14 @@ class CodeModeResourceProvider(AggregatingResourceProvider):
46
51
  self._cached_tool: Tool | None = None
47
52
  self.usage_notes = usage_notes
48
53
 
49
- async def get_tools(self) -> list[Tool]:
54
+ # Filesystem for script history
55
+ from fsspec.implementations.asyn_wrapper import AsyncFileSystemWrapper
56
+ from fsspec.implementations.memory import MemoryFileSystem
57
+
58
+ self._memory_fs = MemoryFileSystem()
59
+ self._fs = AsyncFileSystemWrapper(self._memory_fs)
60
+
61
+ async def get_tools(self) -> Sequence[Tool]:
50
62
  """Return single meta-tool for Python execution with available tools."""
51
63
  # Always generate fresh toolset to reflect current tools
52
64
  toolset_generator = await self._get_code_generator()
@@ -55,9 +67,9 @@ class CodeModeResourceProvider(AggregatingResourceProvider):
55
67
 
56
68
  if self._cached_tool is None:
57
69
  # Create a closure that captures self but isn't a bound method
58
- async def execute_tool(ctx: AgentContext, python_code: str) -> Any:
70
+ async def execute_tool(ctx: AgentContext, python_code: str, title: str) -> Any:
59
71
  """These docstings are overriden by description_override."""
60
- return await self.execute(ctx, python_code)
72
+ return await self.execute(ctx, python_code, title)
61
73
 
62
74
  self._cached_tool = self.create_tool(execute_tool, description_override=desc)
63
75
  else:
@@ -66,11 +78,12 @@ class CodeModeResourceProvider(AggregatingResourceProvider):
66
78
 
67
79
  return [self._cached_tool]
68
80
 
69
- async def execute(self, ctx: AgentContext, python_code: str) -> Any: # noqa: D417
81
+ async def execute(self, ctx: AgentContext, python_code: str, title: str) -> Any: # noqa: D417
70
82
  """Execute Python code with all wrapped tools available as functions.
71
83
 
72
84
  Args:
73
85
  python_code: Python code to execute
86
+ title: Short descriptive title for this script (3-4 words)
74
87
 
75
88
  Returns:
76
89
  Result of the last expression or explicit return value
@@ -93,18 +106,47 @@ class CodeModeResourceProvider(AggregatingResourceProvider):
93
106
  # namespace["report_progress"] = NamespaceCallable(report_progress)
94
107
 
95
108
  validate_code(python_code)
109
+ start_time = datetime.now(UTC)
110
+ exit_code = 0
111
+ error_msg = None
112
+ result_value = None
113
+
96
114
  try:
97
115
  exec(python_code, namespace)
98
- result = await namespace["main"]()
116
+ result_value = await namespace["main"]()
99
117
  # Handle edge cases with coroutines and return values
100
- if inspect.iscoroutine(result):
101
- result = await result
102
- if not result: # in order to not confuse the model, return a success message.
103
- return "Code executed successfully"
118
+ if inspect.iscoroutine(result_value):
119
+ result_value = await result_value
120
+
121
+ if not result_value: # in order to not confuse the model, return a success message.
122
+ result_value = "Code executed successfully"
104
123
  except Exception as e: # noqa: BLE001
105
- return f"Error executing code: {e!s}"
106
- else:
107
- return result
124
+ exit_code = 1
125
+ error_msg = f"{e!s}"
126
+ result_value = f"Error executing code: {error_msg}"
127
+ finally:
128
+ # Save to filesystem
129
+ end_time = datetime.now(UTC)
130
+ duration = (end_time - start_time).total_seconds()
131
+ timestamp = start_time.strftime("%Y%m%d_%H%M%S")
132
+
133
+ # Write script file
134
+ script_path = f"scripts/{timestamp}_{title}.py"
135
+ self._memory_fs.pipe(script_path, python_code.encode("utf-8"))
136
+
137
+ # Write metadata file
138
+ metadata = {
139
+ "title": title,
140
+ "timestamp": start_time.isoformat(),
141
+ "exit_code": exit_code,
142
+ "duration": duration,
143
+ "result": str(result_value),
144
+ "error": error_msg,
145
+ }
146
+ metadata_path = f"scripts/{timestamp}_{title}.json"
147
+ self._memory_fs.pipe(metadata_path, json.dumps(metadata, indent=2).encode("utf-8"))
148
+
149
+ return result_value
108
150
 
109
151
  def invalidate_cache(self) -> None:
110
152
  """Invalidate cached tool when providers change."""
@@ -116,6 +158,16 @@ class CodeModeResourceProvider(AggregatingResourceProvider):
116
158
  tools = await super().get_tools()
117
159
  return tools_to_codegen(tools=tools, include_docstrings=self.include_docstrings)
118
160
 
161
+ def get_fs(self) -> AsyncFileSystem:
162
+ """Get filesystem view of script history.
163
+
164
+ Returns:
165
+ AsyncFileSystem containing:
166
+ - scripts/{timestamp}_{title}.py - Executed scripts
167
+ - scripts/{timestamp}_{title}.json - Execution metadata
168
+ """
169
+ return self._fs
170
+
119
171
 
120
172
  if __name__ == "__main__":
121
173
  import anyio
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
13
13
  from types import TracebackType
14
14
 
15
15
  from exxec.base import ExecutionEnvironment
16
- from exxec.configs import ExecutionEnvironmentConfig
16
+ from exxec_config import ExecutionEnvironmentConfig
17
17
  from schemez import ToolsetCodeGenerator
18
18
 
19
19
  from agentpool.tools.base import Tool
@@ -103,7 +103,7 @@ class RemoteMCPExecutor:
103
103
 
104
104
  if __name__ == "__main__":
105
105
  import anyio
106
- from exxec.configs import LocalExecutionEnvironmentConfig
106
+ from exxec_config import LocalExecutionEnvironmentConfig
107
107
 
108
108
  from agentpool.tools.base import Tool
109
109
 
@@ -6,7 +6,7 @@ import asyncio
6
6
  import contextlib
7
7
  from typing import TYPE_CHECKING, Any, Self
8
8
 
9
- from exxec.configs import LocalExecutionEnvironmentConfig
9
+ from exxec_config import LocalExecutionEnvironmentConfig
10
10
 
11
11
  from agentpool.agents.context import AgentContext # noqa: TC001
12
12
  from agentpool.log import get_logger
@@ -26,7 +26,7 @@ logger = get_logger(__name__)
26
26
  if TYPE_CHECKING:
27
27
  from types import TracebackType
28
28
 
29
- from exxec.configs import ExecutionEnvironmentConfig
29
+ from exxec_config import ExecutionEnvironmentConfig
30
30
 
31
31
  from agentpool.resource_providers import ResourceProvider
32
32
 
@@ -75,11 +75,12 @@ class RemoteCodeModeResourceProvider(CodeModeResourceProvider):
75
75
  self._code_executor: RemoteCodeExecutor | None = None
76
76
  self._provider_lock = asyncio.Lock()
77
77
 
78
- async def execute(self, ctx: AgentContext, python_code: str) -> Any: # noqa: D417
78
+ async def execute(self, ctx: AgentContext, python_code: str, title: str) -> Any: # noqa: D417
79
79
  """Execute Python code in secure environment with tools available via HTTP.
80
80
 
81
81
  Args:
82
82
  python_code: Python code to execute
83
+ title: Short descriptive title for this script (3-4 words)
83
84
 
84
85
  Returns:
85
86
  Result of the code execution
@@ -90,16 +91,12 @@ class RemoteCodeModeResourceProvider(CodeModeResourceProvider):
90
91
  full_code = f"{PROGRESS_HELPER}\n\n{python_code}"
91
92
  logger.info("Complete code", code=full_code)
92
93
  try:
93
- result = await code_provider.execution_env.execute(full_code)
94
- if result.success:
95
- logger.info("Code executed successfully")
96
- if result.result is None:
97
- return "Code executed successfully"
98
- return result.result
94
+ result = await code_provider.execute_code(full_code, title)
99
95
  except Exception as e: # noqa: BLE001
100
96
  return f"Error in secure execution: {e!s}"
101
97
  else:
102
- return f"Error executing code: {result.error}"
98
+ logger.info("Code executed")
99
+ return result
103
100
 
104
101
  async def _get_code_executor(self) -> RemoteCodeExecutor:
105
102
  """Get cached code execution provider with thread-safe initialization."""
@@ -139,7 +136,7 @@ if __name__ == "__main__":
139
136
  import webbrowser
140
137
 
141
138
  import anyio
142
- from exxec.configs import LocalExecutionEnvironmentConfig
139
+ from exxec_config import LocalExecutionEnvironmentConfig
143
140
 
144
141
  from agentpool import Agent, log
145
142
  from agentpool.resource_providers import StaticResourceProvider
@@ -153,7 +150,7 @@ if __name__ == "__main__":
153
150
  async def main() -> None:
154
151
  tools = [Tool.from_callable(open_browser)]
155
152
  static_provider = StaticResourceProvider(tools=tools)
156
- config = LocalExecutionEnvironmentConfig(timeout=30.0)
153
+ config = LocalExecutionEnvironmentConfig(default_command_timeout=30.0)
157
154
  provider = RemoteCodeModeResourceProvider(
158
155
  providers=[static_provider],
159
156
  execution_config=config,
@@ -8,6 +8,8 @@ from agentpool.resource_providers import ResourceProvider
8
8
 
9
9
 
10
10
  if TYPE_CHECKING:
11
+ from collections.abc import Sequence
12
+
11
13
  from agentpool.tools.base import Tool
12
14
 
13
15
 
@@ -32,7 +34,7 @@ class FilteringResourceProvider(ResourceProvider):
32
34
  """Delegate attribute access to wrapped provider."""
33
35
  return getattr(self._provider, name)
34
36
 
35
- async def get_tools(self) -> list[Tool]:
37
+ async def get_tools(self) -> Sequence[Tool]:
36
38
  """Get filtered tools from wrapped provider.
37
39
 
38
40
  Returns only tools where the filter value is True. Tools not in the filter