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
@@ -5,18 +5,11 @@ from __future__ import annotations
5
5
  from typing import TYPE_CHECKING, Any, Literal
6
6
 
7
7
  from pydantic_ai import (
8
- FunctionToolCallEvent,
9
- FunctionToolResultEvent,
10
8
  ModelRetry,
11
9
  PartDeltaEvent,
12
- PartStartEvent,
13
- RetryPromptPart,
14
- TextPart,
15
10
  TextPartDelta,
16
- ThinkingPart,
17
11
  ThinkingPartDelta,
18
12
  ToolCallPartDelta,
19
- ToolReturnPart,
20
13
  )
21
14
 
22
15
  from agentpool.agents.context import AgentContext # noqa: TC001
@@ -129,58 +122,60 @@ async def batch_stream_deltas( # noqa: PLR0915
129
122
  yield _make_batched_event()
130
123
 
131
124
 
132
- async def _stream_agent_with_progress(
125
+ async def _stream_subagent(
133
126
  ctx: AgentContext,
127
+ source_name: str,
128
+ source_type: Literal["agent", "team_parallel", "team_sequential"],
134
129
  stream: AsyncIterator[RichAgentStreamEvent[Any]],
135
130
  *,
136
131
  batch_deltas: bool = False,
132
+ depth: int = 1,
137
133
  ) -> str:
138
- """Stream an agent's execution and emit progress events.
134
+ """Stream a subagent's execution, emitting SubAgentEvents into parent stream.
139
135
 
140
136
  Args:
141
137
  ctx: Agent context for emitting events
138
+ source_name: Name of the subagent/team
139
+ source_type: Whether source is "agent" or "team"
142
140
  stream: Async iterator of stream events from agent.run_stream()
143
141
  batch_deltas: If True, batch consecutive text/thinking deltas for fewer UI updates
142
+ depth: Nesting depth for nested delegation
144
143
 
145
144
  Returns:
146
- Aggregated content from the stream
145
+ Final text content from the stream
147
146
  """
147
+ from agentpool.agents.events import StreamCompleteEvent, SubAgentEvent
148
+
148
149
  if batch_deltas:
149
150
  stream = batch_stream_deltas(stream)
150
151
 
151
- aggregated: list[str] = []
152
+ final_content: str = ""
152
153
  async for event in stream:
153
- match event:
154
- case (
155
- PartStartEvent(part=TextPart(content=delta))
156
- | PartDeltaEvent(delta=TextPartDelta(content_delta=delta))
157
- ):
158
- aggregated.append(delta)
159
- await ctx.events.tool_call_progress("".join(aggregated))
160
- case (
161
- PartStartEvent(part=ThinkingPart(content=delta))
162
- | PartDeltaEvent(delta=ThinkingPartDelta(content_delta=delta))
163
- ):
164
- if delta:
165
- aggregated.append(f"💭 {delta}")
166
- await ctx.events.tool_call_progress("".join(aggregated))
167
- case FunctionToolCallEvent(part=part):
168
- aggregated.append(f"\n🔧 Using tool: {part.tool_name}\n")
169
- await ctx.events.tool_call_progress("".join(aggregated))
170
- case FunctionToolResultEvent(
171
- result=ToolReturnPart(content=content, tool_name=tool_name),
172
- ):
173
- aggregated.append(f"✅ {tool_name}: {content}\n")
174
- await ctx.events.tool_call_progress("".join(aggregated))
154
+ # Handle nested SubAgentEvents - increment depth
155
+ if isinstance(event, SubAgentEvent):
156
+ nested_event = SubAgentEvent(
157
+ source_name=event.source_name,
158
+ source_type=event.source_type,
159
+ event=event.event,
160
+ depth=event.depth + depth,
161
+ )
162
+ await ctx.events.emit_event(nested_event)
163
+ else:
164
+ # Wrap the event in SubAgentEvent
165
+ subagent_event = SubAgentEvent(
166
+ source_name=source_name,
167
+ source_type=source_type,
168
+ event=event,
169
+ depth=depth,
170
+ )
171
+ await ctx.events.emit_event(subagent_event)
175
172
 
176
- case FunctionToolResultEvent(result=RetryPromptPart(tool_name=tool_name) as result):
177
- error_message = result.model_response()
178
- aggregated.append(f"❌ {tool_name or 'unknown'}: {error_message}\n")
179
- await ctx.events.tool_call_progress("".join(aggregated))
180
- case _:
181
- pass
173
+ # Extract final content from StreamCompleteEvent
174
+ if isinstance(event, StreamCompleteEvent):
175
+ content = event.message.content
176
+ final_content = str(content) if content else ""
182
177
 
183
- return "".join(aggregated).strip()
178
+ return final_content
184
179
 
185
180
 
186
181
  class SubagentTools(StaticResourceProvider):
@@ -269,6 +264,10 @@ class SubagentTools(StaticResourceProvider):
269
264
  Returns:
270
265
  The result of the delegated task
271
266
  """
267
+ from agentpool import Team, TeamRun
268
+ from agentpool.agents.base_agent import BaseAgent
269
+ from agentpool.common_types import SupportsRunStream
270
+
272
271
  if not ctx.pool:
273
272
  msg = "Agent needs to be in a pool to delegate tasks"
274
273
  raise ToolError(msg)
@@ -278,15 +277,26 @@ class SubagentTools(StaticResourceProvider):
278
277
  f"Available nodes: {', '.join(ctx.pool.nodes.keys())}"
279
278
  )
280
279
  raise ModelRetry(msg)
280
+ # Determine source type and get node
281
+ node = ctx.pool.nodes[agent_or_team_name]
282
+ match node:
283
+ case Team():
284
+ source_type: Literal["team_parallel", "team_sequential", "agent"] = "team_parallel"
285
+ case TeamRun():
286
+ source_type = "team_sequential"
287
+ case BaseAgent():
288
+ source_type = "agent"
289
+ if not isinstance(node, SupportsRunStream):
290
+ msg = f"Node {agent_or_team_name} does not support streaming"
291
+ raise ToolError(msg)
281
292
 
282
- # For teams, use simple run() - no streaming support yet
283
- if agent_or_team_name in ctx.pool.teams:
284
- result = await ctx.pool.teams[agent_or_team_name].run(prompt)
285
- return result.format(style="detailed", show_costs=True)
286
- # For agents (regular or ACP), stream with progress events
287
- agent = ctx.pool.all_agents[agent_or_team_name]
288
- return await _stream_agent_with_progress(
289
- ctx, agent.run_stream(prompt), batch_deltas=self._batch_stream_deltas
293
+ # Stream with SubAgentEvent wrapping
294
+ return await _stream_subagent(
295
+ ctx,
296
+ source_name=agent_or_team_name,
297
+ source_type=source_type,
298
+ stream=node.run_stream(prompt),
299
+ batch_deltas=self._batch_stream_deltas,
290
300
  )
291
301
 
292
302
  async def ask_agent( # noqa: D417
@@ -315,9 +325,12 @@ class SubagentTools(StaticResourceProvider):
315
325
 
316
326
  agent = ctx.pool.all_agents[agent_name]
317
327
  try:
318
- stream = agent.run_stream(message)
319
- return await _stream_agent_with_progress(
320
- ctx, stream, batch_deltas=self._batch_stream_deltas
328
+ return await _stream_subagent(
329
+ ctx,
330
+ source_name=agent_name,
331
+ source_type="agent",
332
+ stream=agent.run_stream(message),
333
+ batch_deltas=self._batch_stream_deltas,
321
334
  )
322
335
  except Exception as e:
323
336
  msg = f"Failed to ask agent {agent_name}: {e}"
@@ -11,6 +11,8 @@ from agentpool.tools.exceptions import ToolError
11
11
 
12
12
 
13
13
  if TYPE_CHECKING:
14
+ from collections.abc import Sequence
15
+
14
16
  from agentpool.tools.base import Tool
15
17
  from agentpool_config.workers import WorkerConfig
16
18
 
@@ -35,7 +37,7 @@ class WorkersTools(ResourceProvider):
35
37
  super().__init__(name=name)
36
38
  self.workers = workers
37
39
 
38
- async def get_tools(self) -> list[Tool]:
40
+ async def get_tools(self) -> Sequence[Tool]:
39
41
  """Get tools for all configured workers."""
40
42
  return [self._create_worker_tool(i) for i in self.workers]
41
43
 
@@ -78,7 +80,7 @@ class WorkersTools(ResourceProvider):
78
80
  old_history = worker.conversation.get_history()
79
81
  worker.conversation.set_history(ctx.agent.conversation.get_history())
80
82
  elif reset_history_on_run:
81
- worker.conversation.clear()
83
+ await worker.conversation.clear()
82
84
 
83
85
  try:
84
86
  result = await worker.run(prompt)
@@ -10,7 +10,7 @@ from agentpool.resource_providers import ResourceProvider
10
10
 
11
11
 
12
12
  if TYPE_CHECKING:
13
- from collections.abc import Callable
13
+ from collections.abc import Callable, Sequence
14
14
 
15
15
  from agentpool.tools.base import Tool
16
16
 
@@ -52,7 +52,7 @@ class ComposioTools(ResourceProvider):
52
52
  handle_tool_call.__name__ = tool_slug
53
53
  return handle_tool_call
54
54
 
55
- async def get_tools(self) -> list[Tool]:
55
+ async def get_tools(self) -> Sequence[Tool]:
56
56
  """Get tools from composio."""
57
57
  # Return cached tools if available
58
58
  if self._tools is not None:
@@ -12,6 +12,8 @@ from agentpool.resource_providers import ResourceProvider
12
12
 
13
13
 
14
14
  if TYPE_CHECKING:
15
+ from collections.abc import Sequence
16
+
15
17
  from agentpool.tools.base import Tool
16
18
 
17
19
 
@@ -27,7 +29,7 @@ class EntryPointTools(ResourceProvider):
27
29
  self._tools: list[Tool] | None = None
28
30
  self.registry = EntryPointRegistry[Callable[..., Any]]("agentpool")
29
31
 
30
- async def get_tools(self) -> list[Tool]:
32
+ async def get_tools(self) -> Sequence[Tool]:
31
33
  """Get tools from entry points."""
32
34
  # Return cached tools if available
33
35
  if self._tools is not None:
@@ -328,19 +328,27 @@ def _parse_ripgrep_json_output(stdout: str, max_output_bytes: int) -> dict[str,
328
328
  matches: list[dict[str, Any]] = []
329
329
  total_bytes = 0
330
330
  was_truncated = False
331
+ match_count = 0 # Track actual matches (not context lines)
331
332
 
332
333
  for line in stdout.splitlines():
333
334
  if not line.strip():
334
335
  continue
335
336
  try:
336
337
  data = anyenv.load_json(line, return_type=dict)
337
- if data.get("type") == "match":
338
+ entry_type = data.get("type")
339
+
340
+ if entry_type in ("match", "context"):
338
341
  match_data = data.get("data", {})
339
342
  path = match_data.get("path", {}).get("text", "")
340
343
  line_num = match_data.get("line_number", 0)
341
344
  content = match_data.get("lines", {}).get("text", "").rstrip("\n")
342
345
 
343
- match_entry = {"path": path, "line": line_num, "content": content}
346
+ match_entry = {
347
+ "path": path,
348
+ "line": line_num,
349
+ "content": content,
350
+ "is_match": entry_type == "match",
351
+ }
344
352
  entry_size = len(path) + len(str(line_num)) + len(content) + 10
345
353
 
346
354
  if total_bytes + entry_size > max_output_bytes:
@@ -349,6 +357,9 @@ def _parse_ripgrep_json_output(stdout: str, max_output_bytes: int) -> dict[str,
349
357
 
350
358
  matches.append(match_entry)
351
359
  total_bytes += entry_size
360
+
361
+ if entry_type == "match":
362
+ match_count += 1
352
363
  except anyenv.JsonLoadError:
353
364
  continue
354
365
 
@@ -363,11 +374,13 @@ def _parse_ripgrep_json_output(stdout: str, max_output_bytes: int) -> dict[str,
363
374
  # Truncate long lines for readability
364
375
  if len(content) > 100: # noqa: PLR2004
365
376
  content = content[:97] + "..."
366
- table_lines.append(f"| {m['path']} | {m['line']} | {content} |")
377
+ # Mark match lines with indicator if there are context lines
378
+ prefix = "→ " if m.get("is_match") and match_count < len(matches) else ""
379
+ table_lines.append(f"| {m['path']} | {m['line']} | {prefix}{content} |")
367
380
 
368
381
  return {
369
382
  "matches": "\n".join(table_lines),
370
- "match_count": len(matches),
383
+ "match_count": match_count,
371
384
  "was_truncated": was_truncated,
372
385
  }
373
386
 
@@ -406,10 +419,17 @@ def _parse_gnu_grep_output(stdout: str, max_output_bytes: int) -> dict[str, Any]
406
419
 
407
420
  table_lines = ["| File | Line | Content |", "|------|------|---------|"]
408
421
  for m in matches:
422
+ # Handle separator lines
423
+ if m.get("is_separator"):
424
+ table_lines.append("| | | |")
425
+ continue
426
+
409
427
  content = m["content"].replace("|", "\\|")
410
428
  if len(content) > 100: # noqa: PLR2004
411
429
  content = content[:97] + "..."
412
- table_lines.append(f"| {m['path']} | {m['line']} | {content} |")
430
+ # Mark match lines with indicator if there are context lines
431
+ prefix = "→ " if m.get("is_match") else ""
432
+ table_lines.append(f"| {m['path']} | {m['line']} | {prefix}{content} |")
413
433
 
414
434
  return {
415
435
  "matches": "\n".join(table_lines),
@@ -550,8 +550,9 @@ def truncate_lines(
550
550
  Returns:
551
551
  Tuple of (truncated_lines, was_truncated)
552
552
  """
553
- # Apply offset
554
- start_idx = max(0, offset)
553
+ # Apply offset (supports negative indexing like Python lists)
554
+ start_idx = max(0, len(lines) + offset) if offset < 0 else min(offset, len(lines))
555
+
555
556
  if start_idx >= len(lines):
556
557
  return [], False
557
558