agentpool 2.2.3__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 (250) hide show
  1. acp/__init__.py +0 -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/client/connection.py +38 -29
  7. acp/client/implementations/default_client.py +3 -2
  8. acp/client/implementations/headless_client.py +2 -2
  9. acp/connection.py +2 -2
  10. acp/notifications.py +18 -49
  11. acp/schema/__init__.py +2 -0
  12. acp/schema/agent_responses.py +21 -0
  13. acp/schema/client_requests.py +3 -3
  14. acp/schema/session_state.py +63 -29
  15. acp/task/supervisor.py +2 -2
  16. acp/utils.py +2 -2
  17. agentpool/__init__.py +2 -0
  18. agentpool/agents/acp_agent/acp_agent.py +278 -263
  19. agentpool/agents/acp_agent/acp_converters.py +150 -17
  20. agentpool/agents/acp_agent/client_handler.py +35 -24
  21. agentpool/agents/acp_agent/session_state.py +14 -6
  22. agentpool/agents/agent.py +471 -643
  23. agentpool/agents/agui_agent/agui_agent.py +104 -107
  24. agentpool/agents/agui_agent/helpers.py +3 -4
  25. agentpool/agents/base_agent.py +485 -32
  26. agentpool/agents/claude_code_agent/FORKING.md +191 -0
  27. agentpool/agents/claude_code_agent/__init__.py +13 -1
  28. agentpool/agents/claude_code_agent/claude_code_agent.py +654 -334
  29. agentpool/agents/claude_code_agent/converters.py +4 -141
  30. agentpool/agents/claude_code_agent/models.py +77 -0
  31. agentpool/agents/claude_code_agent/static_info.py +100 -0
  32. agentpool/agents/claude_code_agent/usage.py +242 -0
  33. agentpool/agents/events/__init__.py +22 -0
  34. agentpool/agents/events/builtin_handlers.py +65 -0
  35. agentpool/agents/events/event_emitter.py +3 -0
  36. agentpool/agents/events/events.py +84 -3
  37. agentpool/agents/events/infer_info.py +145 -0
  38. agentpool/agents/events/processors.py +254 -0
  39. agentpool/agents/interactions.py +41 -6
  40. agentpool/agents/modes.py +13 -0
  41. agentpool/agents/slashed_agent.py +5 -4
  42. agentpool/agents/tool_wrapping.py +18 -6
  43. agentpool/common_types.py +35 -21
  44. agentpool/config_resources/acp_assistant.yml +2 -2
  45. agentpool/config_resources/agents.yml +3 -0
  46. agentpool/config_resources/agents_template.yml +1 -0
  47. agentpool/config_resources/claude_code_agent.yml +9 -8
  48. agentpool/config_resources/external_acp_agents.yml +2 -1
  49. agentpool/delegation/base_team.py +4 -30
  50. agentpool/delegation/pool.py +104 -265
  51. agentpool/delegation/team.py +57 -57
  52. agentpool/delegation/teamrun.py +50 -55
  53. agentpool/functional/run.py +10 -4
  54. agentpool/mcp_server/client.py +73 -38
  55. agentpool/mcp_server/conversions.py +54 -13
  56. agentpool/mcp_server/manager.py +9 -23
  57. agentpool/mcp_server/registries/official_registry_client.py +10 -1
  58. agentpool/mcp_server/tool_bridge.py +114 -79
  59. agentpool/messaging/connection_manager.py +11 -10
  60. agentpool/messaging/event_manager.py +5 -5
  61. agentpool/messaging/message_container.py +6 -30
  62. agentpool/messaging/message_history.py +87 -8
  63. agentpool/messaging/messagenode.py +52 -14
  64. agentpool/messaging/messages.py +2 -26
  65. agentpool/messaging/processing.py +10 -22
  66. agentpool/models/__init__.py +1 -1
  67. agentpool/models/acp_agents/base.py +6 -2
  68. agentpool/models/acp_agents/mcp_capable.py +124 -15
  69. agentpool/models/acp_agents/non_mcp.py +0 -23
  70. agentpool/models/agents.py +66 -66
  71. agentpool/models/agui_agents.py +1 -1
  72. agentpool/models/claude_code_agents.py +111 -17
  73. agentpool/models/file_parsing.py +0 -1
  74. agentpool/models/manifest.py +70 -50
  75. agentpool/prompts/conversion_manager.py +1 -1
  76. agentpool/prompts/prompts.py +5 -2
  77. agentpool/resource_providers/__init__.py +2 -0
  78. agentpool/resource_providers/aggregating.py +4 -2
  79. agentpool/resource_providers/base.py +13 -3
  80. agentpool/resource_providers/codemode/code_executor.py +72 -5
  81. agentpool/resource_providers/codemode/helpers.py +2 -2
  82. agentpool/resource_providers/codemode/provider.py +64 -12
  83. agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
  84. agentpool/resource_providers/codemode/remote_provider.py +9 -12
  85. agentpool/resource_providers/filtering.py +3 -1
  86. agentpool/resource_providers/mcp_provider.py +66 -12
  87. agentpool/resource_providers/plan_provider.py +111 -18
  88. agentpool/resource_providers/pool.py +5 -3
  89. agentpool/resource_providers/resource_info.py +111 -0
  90. agentpool/resource_providers/static.py +2 -2
  91. agentpool/sessions/__init__.py +2 -0
  92. agentpool/sessions/manager.py +2 -3
  93. agentpool/sessions/models.py +9 -6
  94. agentpool/sessions/protocol.py +28 -0
  95. agentpool/sessions/session.py +11 -55
  96. agentpool/storage/manager.py +361 -54
  97. agentpool/talk/registry.py +4 -4
  98. agentpool/talk/talk.py +9 -10
  99. agentpool/testing.py +1 -1
  100. agentpool/tool_impls/__init__.py +6 -0
  101. agentpool/tool_impls/agent_cli/__init__.py +42 -0
  102. agentpool/tool_impls/agent_cli/tool.py +95 -0
  103. agentpool/tool_impls/bash/__init__.py +64 -0
  104. agentpool/tool_impls/bash/helpers.py +35 -0
  105. agentpool/tool_impls/bash/tool.py +171 -0
  106. agentpool/tool_impls/delete_path/__init__.py +70 -0
  107. agentpool/tool_impls/delete_path/tool.py +142 -0
  108. agentpool/tool_impls/download_file/__init__.py +80 -0
  109. agentpool/tool_impls/download_file/tool.py +183 -0
  110. agentpool/tool_impls/execute_code/__init__.py +55 -0
  111. agentpool/tool_impls/execute_code/tool.py +163 -0
  112. agentpool/tool_impls/grep/__init__.py +80 -0
  113. agentpool/tool_impls/grep/tool.py +200 -0
  114. agentpool/tool_impls/list_directory/__init__.py +73 -0
  115. agentpool/tool_impls/list_directory/tool.py +197 -0
  116. agentpool/tool_impls/question/__init__.py +42 -0
  117. agentpool/tool_impls/question/tool.py +127 -0
  118. agentpool/tool_impls/read/__init__.py +104 -0
  119. agentpool/tool_impls/read/tool.py +305 -0
  120. agentpool/tools/__init__.py +2 -1
  121. agentpool/tools/base.py +114 -34
  122. agentpool/tools/manager.py +57 -1
  123. agentpool/ui/base.py +2 -2
  124. agentpool/ui/mock_provider.py +2 -2
  125. agentpool/ui/stdlib_provider.py +2 -2
  126. agentpool/utils/streams.py +21 -96
  127. agentpool/vfs_registry.py +7 -2
  128. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/METADATA +16 -22
  129. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/RECORD +242 -195
  130. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
  131. agentpool_cli/__main__.py +20 -0
  132. agentpool_cli/create.py +1 -1
  133. agentpool_cli/serve_acp.py +59 -1
  134. agentpool_cli/serve_opencode.py +1 -1
  135. agentpool_cli/ui.py +557 -0
  136. agentpool_commands/__init__.py +12 -5
  137. agentpool_commands/agents.py +1 -1
  138. agentpool_commands/pool.py +260 -0
  139. agentpool_commands/session.py +1 -1
  140. agentpool_commands/text_sharing/__init__.py +119 -0
  141. agentpool_commands/text_sharing/base.py +123 -0
  142. agentpool_commands/text_sharing/github_gist.py +80 -0
  143. agentpool_commands/text_sharing/opencode.py +462 -0
  144. agentpool_commands/text_sharing/paste_rs.py +59 -0
  145. agentpool_commands/text_sharing/pastebin.py +116 -0
  146. agentpool_commands/text_sharing/shittycodingagent.py +112 -0
  147. agentpool_commands/utils.py +31 -32
  148. agentpool_config/__init__.py +30 -2
  149. agentpool_config/agentpool_tools.py +498 -0
  150. agentpool_config/converters.py +1 -1
  151. agentpool_config/event_handlers.py +42 -0
  152. agentpool_config/events.py +1 -1
  153. agentpool_config/forward_targets.py +1 -4
  154. agentpool_config/jinja.py +3 -3
  155. agentpool_config/mcp_server.py +1 -5
  156. agentpool_config/nodes.py +1 -1
  157. agentpool_config/observability.py +44 -0
  158. agentpool_config/session.py +0 -3
  159. agentpool_config/storage.py +38 -39
  160. agentpool_config/task.py +3 -3
  161. agentpool_config/tools.py +11 -28
  162. agentpool_config/toolsets.py +22 -90
  163. agentpool_server/a2a_server/agent_worker.py +307 -0
  164. agentpool_server/a2a_server/server.py +23 -18
  165. agentpool_server/acp_server/acp_agent.py +125 -56
  166. agentpool_server/acp_server/commands/acp_commands.py +46 -216
  167. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +8 -7
  168. agentpool_server/acp_server/event_converter.py +651 -0
  169. agentpool_server/acp_server/input_provider.py +53 -10
  170. agentpool_server/acp_server/server.py +1 -11
  171. agentpool_server/acp_server/session.py +90 -410
  172. agentpool_server/acp_server/session_manager.py +8 -34
  173. agentpool_server/agui_server/server.py +3 -1
  174. agentpool_server/mcp_server/server.py +5 -2
  175. agentpool_server/opencode_server/ENDPOINTS.md +53 -14
  176. agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
  177. agentpool_server/opencode_server/__init__.py +0 -8
  178. agentpool_server/opencode_server/converters.py +132 -26
  179. agentpool_server/opencode_server/input_provider.py +160 -8
  180. agentpool_server/opencode_server/models/__init__.py +42 -20
  181. agentpool_server/opencode_server/models/app.py +12 -0
  182. agentpool_server/opencode_server/models/events.py +203 -29
  183. agentpool_server/opencode_server/models/mcp.py +19 -0
  184. agentpool_server/opencode_server/models/message.py +18 -1
  185. agentpool_server/opencode_server/models/parts.py +134 -1
  186. agentpool_server/opencode_server/models/question.py +56 -0
  187. agentpool_server/opencode_server/models/session.py +13 -1
  188. agentpool_server/opencode_server/routes/__init__.py +4 -0
  189. agentpool_server/opencode_server/routes/agent_routes.py +33 -2
  190. agentpool_server/opencode_server/routes/app_routes.py +66 -3
  191. agentpool_server/opencode_server/routes/config_routes.py +66 -5
  192. agentpool_server/opencode_server/routes/file_routes.py +184 -5
  193. agentpool_server/opencode_server/routes/global_routes.py +1 -1
  194. agentpool_server/opencode_server/routes/lsp_routes.py +1 -1
  195. agentpool_server/opencode_server/routes/message_routes.py +122 -66
  196. agentpool_server/opencode_server/routes/permission_routes.py +63 -0
  197. agentpool_server/opencode_server/routes/pty_routes.py +23 -22
  198. agentpool_server/opencode_server/routes/question_routes.py +128 -0
  199. agentpool_server/opencode_server/routes/session_routes.py +139 -68
  200. agentpool_server/opencode_server/routes/tui_routes.py +1 -1
  201. agentpool_server/opencode_server/server.py +47 -2
  202. agentpool_server/opencode_server/state.py +30 -0
  203. agentpool_storage/__init__.py +0 -4
  204. agentpool_storage/base.py +81 -2
  205. agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
  206. agentpool_storage/claude_provider/__init__.py +42 -0
  207. agentpool_storage/{claude_provider.py → claude_provider/provider.py} +190 -8
  208. agentpool_storage/file_provider.py +149 -15
  209. agentpool_storage/memory_provider.py +132 -12
  210. agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
  211. agentpool_storage/opencode_provider/__init__.py +16 -0
  212. agentpool_storage/opencode_provider/helpers.py +414 -0
  213. agentpool_storage/opencode_provider/provider.py +895 -0
  214. agentpool_storage/session_store.py +20 -6
  215. agentpool_storage/sql_provider/sql_provider.py +135 -2
  216. agentpool_storage/sql_provider/utils.py +2 -12
  217. agentpool_storage/zed_provider/__init__.py +16 -0
  218. agentpool_storage/zed_provider/helpers.py +281 -0
  219. agentpool_storage/zed_provider/models.py +130 -0
  220. agentpool_storage/zed_provider/provider.py +442 -0
  221. agentpool_storage/zed_provider.py +803 -0
  222. agentpool_toolsets/__init__.py +0 -2
  223. agentpool_toolsets/builtin/__init__.py +2 -4
  224. agentpool_toolsets/builtin/code.py +4 -4
  225. agentpool_toolsets/builtin/debug.py +115 -40
  226. agentpool_toolsets/builtin/execution_environment.py +54 -165
  227. agentpool_toolsets/builtin/skills.py +0 -77
  228. agentpool_toolsets/builtin/subagent_tools.py +64 -51
  229. agentpool_toolsets/builtin/workers.py +4 -2
  230. agentpool_toolsets/composio_toolset.py +2 -2
  231. agentpool_toolsets/entry_points.py +3 -1
  232. agentpool_toolsets/fsspec_toolset/grep.py +25 -5
  233. agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
  234. agentpool_toolsets/fsspec_toolset/toolset.py +350 -66
  235. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  236. agentpool_toolsets/mcp_discovery/toolset.py +74 -17
  237. agentpool_toolsets/mcp_run_toolset.py +8 -11
  238. agentpool_toolsets/notifications.py +33 -33
  239. agentpool_toolsets/openapi.py +3 -1
  240. agentpool_toolsets/search_toolset.py +3 -1
  241. agentpool_config/resources.py +0 -33
  242. agentpool_server/acp_server/acp_tools.py +0 -43
  243. agentpool_server/acp_server/commands/spawn.py +0 -210
  244. agentpool_storage/opencode_provider.py +0 -730
  245. agentpool_storage/text_log_provider.py +0 -276
  246. agentpool_toolsets/builtin/chain.py +0 -288
  247. agentpool_toolsets/builtin/user_interaction.py +0 -52
  248. agentpool_toolsets/semantic_memory_toolset.py +0 -536
  249. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
  250. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,276 +0,0 @@
1
- """Text-based storage provider with dynamic paths."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import TYPE_CHECKING, Any, ClassVar
6
-
7
- from upathtools import to_upath
8
-
9
- from agentpool.log import get_logger
10
- from agentpool.utils.now import get_now
11
- from agentpool_storage.base import StorageProvider
12
-
13
-
14
- if TYPE_CHECKING:
15
- from datetime import datetime
16
-
17
- from jinja2 import Template
18
- from upathtools import JoinablePathLike, UPath
19
-
20
- from agentpool.common_types import JsonValue
21
- from agentpool_config.storage import LogFormat, TextLogConfig
22
-
23
-
24
- logger = get_logger(__name__)
25
-
26
-
27
- CONVERSATIONS_TEMPLATE = """\
28
- === AgentPool Log ===
29
-
30
- {%- for conv_id, conv in conversations.items() %}
31
- === Conversation {{ conv_id }} (agent: {{ conv.agent_name }}, started: {{ conv.start_time.strftime('%Y-%m-%d %H:%M:%S') }}) ===
32
-
33
- {%- for msg in messages if msg.conversation_id == conv_id %}
34
- [{{ msg.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}] {{ msg.sender }}{% if msg.model %} ({{ msg.model }}){% endif %}: {{ msg.content }}
35
- {%- if msg.cost_info %}
36
- Tokens: {{ msg.cost_info.token_usage.total }} (prompt: {{ msg.cost_info.token_usage.prompt }}, completion: {{ msg.cost_info.token_usage.completion }})
37
- Cost: ${{ "%.4f"|format(msg.cost_info.total_cost) }}
38
- {%- endif %}
39
- {%- if msg.response_time %}
40
- Response time: {{ "%.1f"|format(msg.response_time) }}s
41
- {%- endif %}
42
- {%- if msg.forwarded_from %}
43
- Forwarded via: {{ msg.forwarded_from|join(' -> ') }}
44
- {%- endif %}
45
-
46
- {%- for tool in tool_calls if tool.message_id == msg.id %}
47
- Tool Call: {{ tool.tool_name }}
48
- Args: {{ tool.args|pprint }}
49
- Result: {{ tool.result }}
50
- {%- endfor %}
51
- {%- endfor %}
52
- {%- endfor %}
53
-
54
- === Commands ===
55
- {%- for cmd in commands %}
56
- [{{ cmd.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}] {{ cmd.agent_name }} ({{ cmd.session_id }}): {{ cmd.command }}
57
- {%- endfor %}
58
- """ # noqa: E501
59
-
60
- CHRONOLOGICAL_TEMPLATE = """\
61
- === AgentPool Log ===
62
-
63
- {%- for entry in entries|sort(attribute="timestamp") %}
64
- {%- if entry.type == "conversation_start" %}
65
- === Conversation {{ entry.conversation_id }} (agent: {{ entry.agent_name }}) started ===
66
-
67
- {%- elif entry.type == "message" %}
68
- [{{ entry.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}] {{ entry.sender }}{% if entry.model %} ({{ entry.model }}){% endif %}: {{ entry.content }}
69
- {%- if entry.cost_info %}
70
- Tokens: {{ entry.cost_info.token_usage.total }} (prompt: {{ entry.cost_info.token_usage.prompt }}, completion: {{ entry.cost_info.token_usage.completion }})
71
- Cost: ${{ "%.4f"|format(entry.cost_info.total_cost) }}
72
- {%- endif %}
73
- {%- if entry.response_time %}
74
- Response time: {{ "%.1f"|format(entry.response_time) }}s
75
- {%- endif %}
76
- {%- if entry.forwarded_from %}
77
- Forwarded via: {{ entry.forwarded_from|join(' -> ') }}
78
- {%- endif %}
79
-
80
- {%- elif entry.type == "tool_call" %}
81
- [{{ entry.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}] Tool Call: {{ entry.tool_name }}
82
- Args: {{ entry.args|pprint }}
83
- Result: {{ entry.result }}
84
-
85
- {%- elif entry.type == "command" %}
86
- [{{ entry.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}] Command by {{ entry.agent_name }}: {{ entry.command }}
87
-
88
- {%- endif %}
89
- {%- endfor %}
90
- """ # noqa: E501
91
-
92
-
93
- class TextLogProvider(StorageProvider):
94
- """Human-readable text log provider with dynamic paths.
95
-
96
- Available template variables:
97
- - now: datetime - Current timestamp
98
- - date: date - Current date
99
- - operation: str - Type of operation (message/conversation/tool_call/command)
100
- - conversation_id: str - ID of current conversation
101
- - agent_name: str - Name of the agent
102
- - content: str - Message content
103
- - role: str - Message role
104
- - model: str - Model name
105
- - session_id: str - Session ID
106
- - tool_name: str - Name of tool being called
107
- - command: str - Command being executed
108
-
109
- All variables default to empty string if not available for current operation.
110
- """
111
-
112
- TEMPLATES: ClassVar[dict[LogFormat, str]] = {
113
- "chronological": CHRONOLOGICAL_TEMPLATE,
114
- "conversations": CONVERSATIONS_TEMPLATE,
115
- }
116
- can_load_history = False
117
-
118
- def __init__(self, config: TextLogConfig) -> None:
119
- """Initialize text log provider."""
120
- from jinja2 import Environment, Undefined
121
-
122
- class EmptyStringUndefined(Undefined):
123
- """Return empty string for undefined variables."""
124
-
125
- def __str__(self) -> str:
126
- return ""
127
-
128
- super().__init__(config)
129
- self.encoding = config.encoding
130
- self.content_template = self._load_template(config.template)
131
- # Configure Jinja env with empty string for undefined
132
- env = Environment(undefined=EmptyStringUndefined, enable_async=True)
133
- self.path_template = env.from_string(config.path)
134
-
135
- self._entries: list[dict[str, Any]] = []
136
-
137
- def _load_template(
138
- self,
139
- template: LogFormat | JoinablePathLike | None,
140
- ) -> Template:
141
- """Load template from predefined or file."""
142
- from jinja2 import Template
143
-
144
- if template is None:
145
- template_str = self.TEMPLATES["chronological"]
146
- elif template in self.TEMPLATES:
147
- template_str = self.TEMPLATES[template] # type: ignore
148
- else: # Assume it's a path
149
- template_str = to_upath(template).read_text()
150
- return Template(template_str)
151
-
152
- def _get_base_context(self, operation: str) -> dict[str, Any]:
153
- """Get base context with defaults.
154
-
155
- Args:
156
- operation: Type of operation being logged
157
-
158
- Returns:
159
- Base context dict with defaults
160
- """
161
- # All other variables will default to empty string via EmptyStringUndefined
162
- return {"now": get_now(), "date": get_now().date(), "operation": operation}
163
-
164
- async def _get_path(self, operation: str, **context: Any) -> UPath:
165
- """Render path template with context.
166
-
167
- Args:
168
- operation: Type of operation being logged
169
- **context: Additional context variables
170
-
171
- Returns:
172
- Concrete path for current operation
173
- """
174
- # Combine base context with provided values
175
- path_context = self._get_base_context(operation)
176
- path_context.update(context)
177
-
178
- path = await self.path_template.render_async(**path_context)
179
- resolved_path = to_upath(path)
180
- resolved_path.parent.mkdir(parents=True, exist_ok=True)
181
- return resolved_path
182
-
183
- async def log_message(
184
- self,
185
- *,
186
- conversation_id: str,
187
- message_id: str,
188
- content: str,
189
- role: str,
190
- name: str | None = None,
191
- cost_info: Any | None = None,
192
- model: str | None = None,
193
- response_time: float | None = None,
194
- forwarded_from: list[str] | None = None,
195
- provider_name: str | None = None,
196
- provider_response_id: str | None = None,
197
- messages: str | None = None,
198
- finish_reason: str | None = None,
199
- parent_id: str | None = None,
200
- ) -> None:
201
- """Store message and update log."""
202
- entry = {
203
- "type": "message",
204
- "timestamp": get_now(),
205
- "conversation_id": conversation_id,
206
- "message_id": message_id,
207
- "content": content,
208
- "role": role,
209
- "agent_name": name,
210
- "model": model,
211
- "cost_info": cost_info,
212
- "response_time": response_time,
213
- "forwarded_from": forwarded_from,
214
- "provider_name": provider_name,
215
- "provider_response_id": provider_response_id,
216
- "messages": messages,
217
- "finish_reason": finish_reason,
218
- }
219
- self._entries.append(entry)
220
-
221
- path = await self._get_path("message", **entry)
222
- self._write(path)
223
-
224
- async def log_conversation(
225
- self,
226
- *,
227
- conversation_id: str,
228
- node_name: str,
229
- start_time: datetime | None = None,
230
- ) -> None:
231
- """Store conversation start."""
232
- entry = {
233
- "type": "conversation",
234
- "timestamp": start_time or get_now(),
235
- "conversation_id": conversation_id,
236
- "agent_name": node_name,
237
- }
238
- self._entries.append(entry)
239
-
240
- path = await self._get_path("conversation", **entry)
241
- self._write(path)
242
-
243
- async def log_command(
244
- self,
245
- *,
246
- agent_name: str,
247
- session_id: str,
248
- command: str,
249
- context_type: type | None = None,
250
- metadata: dict[str, JsonValue] | None = None,
251
- ) -> None:
252
- """Store command."""
253
- entry = {
254
- "type": "command",
255
- "timestamp": get_now(),
256
- "agent_name": agent_name,
257
- "session_id": session_id,
258
- "command": command,
259
- "context_type": context_type.__name__ if context_type else "",
260
- "metadata": metadata or {},
261
- }
262
- self._entries.append(entry)
263
-
264
- path = await self._get_path("command", **entry)
265
- self._write(path)
266
-
267
- def _write(self, path: UPath) -> None:
268
- """Write current state to file at given path."""
269
- context = {"entries": self._entries}
270
- try:
271
- text = self.content_template.render(context)
272
- path.write_text(text, encoding=self.encoding)
273
- except Exception as e:
274
- logger.exception("Failed to write to log file", path=path)
275
- msg = f"Failed to write to log file: {e}"
276
- raise RuntimeError(msg) from e
@@ -1,288 +0,0 @@
1
- """Tool to chain multiple function calls."""
2
-
3
- from __future__ import annotations
4
-
5
- import asyncio
6
- from dataclasses import dataclass
7
- from enum import StrEnum
8
- from typing import Any, Literal, assert_never
9
-
10
- import anyio
11
- from pydantic import Field
12
- from pydantic_ai import ModelRetry
13
- from schemez import Schema
14
-
15
- from agentpool.agents.context import AgentContext # noqa: TC001
16
-
17
-
18
- class ErrorStrategy(StrEnum):
19
- """Strategy for handling errors in the pipeline."""
20
-
21
- STOP = "stop" # Stop pipeline on error
22
- SKIP = "skip" # Skip failed step, continue with previous result
23
- DEFAULT = "default" # Use provided default value
24
- RETRY = "retry" # Retry the step
25
-
26
-
27
- class StepCondition(Schema):
28
- """Condition for conditional execution."""
29
-
30
- field: str # Field to check in result
31
- operator: Literal["eq", "gt", "lt", "contains", "exists"]
32
- value: Any = None
33
-
34
- def evaluate_with_value(self, value: Any) -> bool:
35
- """Evaluate this condition against a value.
36
-
37
- Args:
38
- value: The value to evaluate against the condition.
39
-
40
- Returns:
41
- bool: True if the condition is met, False otherwise.
42
- """
43
- field_value = value.get(self.field) if isinstance(value, dict) else value
44
-
45
- match self.operator:
46
- case "eq":
47
- return bool(field_value == self.value)
48
- case "gt":
49
- return bool(field_value > self.value)
50
- case "lt":
51
- return bool(field_value < self.value)
52
- case "contains":
53
- try:
54
- return self.value in field_value # type: ignore[operator]
55
- except TypeError:
56
- return False
57
- case "exists":
58
- return field_value is not None
59
- case _ as unreachable:
60
- assert_never(unreachable)
61
-
62
-
63
- @dataclass
64
- class StepResult:
65
- """Result of a pipeline step execution."""
66
-
67
- success: bool
68
- result: Any
69
- error: Exception | None = None
70
- retries: int = 0
71
- duration: float = 0.0
72
-
73
-
74
- # Type alias for step results during execution
75
- type StepResults = dict[str, StepResult]
76
-
77
-
78
- class PipelineStep(Schema):
79
- """Single step in a tool pipeline."""
80
-
81
- tool: str
82
- input_kwarg: str = "text"
83
- keyword_args: dict[str, Any] = Field(default_factory=dict)
84
- name: str | None = None # Optional step name for referencing
85
- condition: StepCondition | None = None # Conditional execution
86
- error_strategy: ErrorStrategy = ErrorStrategy.STOP
87
- default_value: Any = None # Used with ErrorStrategy.DEFAULT
88
- max_retries: int = 0
89
- retry_delay: float = 1.0
90
- timeout: float | None = None
91
- depends_on: list[str] = Field(default_factory=list) # Step dependencies
92
-
93
-
94
- class Pipeline(Schema):
95
- """A pipeline of tool operations."""
96
-
97
- input: str | dict[str, Any]
98
- steps: list[PipelineStep]
99
- mode: Literal["sequential", "parallel"] = "sequential"
100
- max_parallel: int = 5 # Max concurrent steps
101
- collect_metrics: bool = False # Collect execution metrics
102
-
103
-
104
- async def _execute_step(
105
- ctx: AgentContext,
106
- step: PipelineStep,
107
- input_value: Any,
108
- results: StepResults,
109
- ) -> StepResult:
110
- """Execute a single pipeline step."""
111
- start_time = asyncio.get_event_loop().time()
112
- retries = 0
113
-
114
- while True:
115
- try:
116
- # Check condition if any
117
- if step.condition and not step.condition.evaluate_with_value(input_value):
118
- return StepResult(success=True, result=input_value, duration=0)
119
-
120
- tool_info = await ctx.agent.tools.get_tool(step.tool) # Get the tool
121
- if isinstance(input_value, dict): # Prepare kwargs
122
- kwargs = {**input_value, **step.keyword_args}
123
- else:
124
- kwargs = {step.input_kwarg: input_value, **step.keyword_args}
125
-
126
- # Execute with timeout if specified
127
- if step.timeout:
128
- fut = tool_info.execute(ctx, **kwargs)
129
- result = await asyncio.wait_for(fut, timeout=step.timeout)
130
- else:
131
- result = await tool_info.execute(ctx, **kwargs)
132
-
133
- duration = asyncio.get_event_loop().time() - start_time
134
- return StepResult(success=True, result=result, duration=duration)
135
-
136
- except Exception as exc:
137
- match step.error_strategy:
138
- case ErrorStrategy.STOP:
139
- raise
140
-
141
- case ErrorStrategy.SKIP:
142
- duration = asyncio.get_event_loop().time() - start_time
143
- return StepResult(
144
- success=False,
145
- result=input_value,
146
- error=exc,
147
- duration=duration,
148
- )
149
-
150
- case ErrorStrategy.DEFAULT:
151
- duration = asyncio.get_event_loop().time() - start_time
152
- return StepResult(
153
- success=False,
154
- result=step.default_value,
155
- error=exc,
156
- duration=duration,
157
- )
158
-
159
- case ErrorStrategy.RETRY:
160
- retries += 1
161
- if retries <= step.max_retries:
162
- await anyio.sleep(step.retry_delay)
163
- continue
164
- raise # Max retries exceeded
165
-
166
-
167
- async def _execute_sequential(ctx: AgentContext, pipeline: Pipeline, results: StepResults) -> Any:
168
- """Execute steps sequentially."""
169
- current = pipeline.input
170
- for step in pipeline.steps:
171
- result = await _execute_step(ctx, step, current, results)
172
- if step.name:
173
- results[step.name] = result
174
- current = result.result
175
- return current
176
-
177
-
178
- async def _execute_parallel(ctx: AgentContext, pipeline: Pipeline, results: StepResults) -> Any:
179
- """Execute independent steps in parallel."""
180
- semaphore = asyncio.Semaphore(pipeline.max_parallel)
181
-
182
- async def run_step(step: PipelineStep) -> None:
183
- async with semaphore:
184
- # Wait for dependencies
185
- for dep in step.depends_on:
186
- while dep not in results:
187
- await anyio.sleep(0.1)
188
-
189
- # Get input from dependency or pipeline input
190
- input_value = results[step.depends_on[-1]].result if step.depends_on else pipeline.input
191
- result = await _execute_step(ctx, step, input_value, results)
192
- if step.name:
193
- results[step.name] = result
194
-
195
- # Create tasks for all steps
196
- tasks = [run_step(step) for step in pipeline.steps]
197
- await asyncio.gather(*tasks)
198
- # Return last result
199
- return results[name].result if (name := pipeline.steps[-1].name) else None
200
-
201
-
202
- async def chain_tools(
203
- ctx: AgentContext,
204
- input_data: str | dict[str, Any],
205
- steps: list[dict[str, Any]],
206
- mode: Literal["sequential", "parallel"] = "sequential",
207
- max_parallel: int = 5,
208
- collect_metrics: bool = False,
209
- ) -> Any:
210
- """Execute multiple tool operations in sequence or parallel.
211
-
212
- WHEN TO USE THIS TOOL:
213
- - Use this when you can plan multiple operations confidently in advance
214
- - Use this for common sequences you've successfully used before
215
- - Use this to reduce interaction rounds for known operation patterns
216
- - Use this when all steps are independent of intermediate results
217
-
218
- DO NOT USE THIS TOOL:
219
- - When you need to inspect intermediate results
220
- - When next steps depend on analyzing previous results
221
- - When you're unsure about the complete sequence
222
- - When you need to handle errors at each step individually
223
-
224
- Args:
225
- ctx: Agent context for tool execution
226
- input_data: Initial input for the pipeline
227
- steps: List of step configurations, each containing:
228
- - tool: Name of the tool to execute
229
- - input_kwarg: Keyword argument name for input (default: "text")
230
- - keyword_args: Additional keyword arguments
231
- - name: Optional step name for referencing
232
- - condition: Optional execution condition
233
- - error_strategy: How to handle errors ("stop", "skip", "default", "retry")
234
- - default_value: Value to use with "default" error strategy
235
- - max_retries: Maximum retry attempts
236
- - retry_delay: Delay between retries in seconds
237
- - timeout: Step timeout in seconds
238
- - depends_on: List of step names this depends on
239
- mode: Execution mode - "sequential" or "parallel"
240
- max_parallel: Maximum concurrent steps for parallel mode
241
- collect_metrics: Whether to collect execution metrics
242
-
243
- Examples:
244
- # Sequential processing
245
- await chain_tools(
246
- ctx,
247
- input_data="main.py",
248
- steps=[
249
- {"tool": "load_resource", "input_kwarg": "name"},
250
- {"tool": "analyze_code", "input_kwarg": "code"},
251
- {"tool": "format_output", "input_kwarg": "text"}
252
- ]
253
- )
254
-
255
- # Parallel processing with dependencies
256
- await chain_tools(
257
- ctx,
258
- input_data="test.py",
259
- mode="parallel",
260
- steps=[
261
- {"tool": "load_resource", "input_kwarg": "name", "name": "load"},
262
- {"tool": "analyze_code", "input_kwarg": "code", "depends_on": ["load"]},
263
- {"tool": "count_tokens", "input_kwarg": "text", "depends_on": ["load"]}
264
- ]
265
- )
266
- """
267
- try:
268
- pipeline = Pipeline(
269
- input=input_data,
270
- steps=[PipelineStep.model_validate(step) for step in steps],
271
- mode=mode,
272
- max_parallel=max_parallel,
273
- collect_metrics=collect_metrics,
274
- )
275
- except Exception as e:
276
- msg = f"Invalid pipeline configuration: {e}"
277
- raise ModelRetry(msg) from e
278
- results: StepResults = {}
279
-
280
- try:
281
- match pipeline.mode:
282
- case "sequential":
283
- return await _execute_sequential(ctx, pipeline, results)
284
- case "parallel":
285
- return await _execute_parallel(ctx, pipeline, results)
286
- except Exception as e:
287
- msg = f"Failed to execute pipeline: {e}"
288
- raise ModelRetry(msg) from e
@@ -1,52 +0,0 @@
1
- """Provider for user interaction tools."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import Any, assert_never
6
-
7
- from agentpool.agents.context import AgentContext # noqa: TC001
8
- from agentpool.resource_providers import StaticResourceProvider
9
-
10
-
11
- async def ask_user( # noqa: D417
12
- ctx: AgentContext,
13
- prompt: str,
14
- response_schema: dict[str, Any] | None = None,
15
- ) -> str:
16
- """Allow LLM to ask user a clarifying question during processing.
17
-
18
- This tool enables agents to ask users for additional information or clarification
19
- when needed to complete a task effectively.
20
-
21
- Args:
22
- prompt: Question to ask the user
23
- response_schema: Optional JSON schema for structured response (defaults to string)
24
-
25
- Returns:
26
- The user's response as a string
27
- """
28
- from mcp.types import ElicitRequestFormParams, ElicitResult, ErrorData
29
-
30
- schema = response_schema or {"type": "string"} # string schema if no none provided
31
- params = ElicitRequestFormParams(message=prompt, requestedSchema=schema)
32
- result = await ctx.handle_elicitation(params)
33
-
34
- match result:
35
- case ElicitResult(action="accept", content=content):
36
- return str(content)
37
- case ElicitResult(action="cancel"):
38
- return "User cancelled the request"
39
- case ElicitResult():
40
- return "User declined to answer"
41
- case ErrorData(message=message):
42
- return f"Error: {message}"
43
- case _ as unreachable:
44
- assert_never(unreachable)
45
-
46
-
47
- class UserInteractionTools(StaticResourceProvider):
48
- """Provider for user interaction tools."""
49
-
50
- def __init__(self, name: str = "user_interaction") -> None:
51
- super().__init__(name=name)
52
- self._tools = [self.create_tool(ask_user, category="other", open_world=True)]