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.
- acp/__init__.py +0 -4
- acp/acp_requests.py +20 -77
- acp/agent/connection.py +8 -0
- acp/agent/implementations/debug_server/debug_server.py +6 -2
- acp/agent/protocol.py +6 -0
- acp/client/connection.py +38 -29
- acp/client/implementations/default_client.py +3 -2
- acp/client/implementations/headless_client.py +2 -2
- acp/connection.py +2 -2
- acp/notifications.py +18 -49
- acp/schema/__init__.py +2 -0
- acp/schema/agent_responses.py +21 -0
- acp/schema/client_requests.py +3 -3
- acp/schema/session_state.py +63 -29
- acp/task/supervisor.py +2 -2
- acp/utils.py +2 -2
- agentpool/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +278 -263
- agentpool/agents/acp_agent/acp_converters.py +150 -17
- agentpool/agents/acp_agent/client_handler.py +35 -24
- agentpool/agents/acp_agent/session_state.py +14 -6
- agentpool/agents/agent.py +471 -643
- agentpool/agents/agui_agent/agui_agent.py +104 -107
- agentpool/agents/agui_agent/helpers.py +3 -4
- agentpool/agents/base_agent.py +485 -32
- agentpool/agents/claude_code_agent/FORKING.md +191 -0
- agentpool/agents/claude_code_agent/__init__.py +13 -1
- agentpool/agents/claude_code_agent/claude_code_agent.py +654 -334
- agentpool/agents/claude_code_agent/converters.py +4 -141
- agentpool/agents/claude_code_agent/models.py +77 -0
- agentpool/agents/claude_code_agent/static_info.py +100 -0
- agentpool/agents/claude_code_agent/usage.py +242 -0
- agentpool/agents/events/__init__.py +22 -0
- agentpool/agents/events/builtin_handlers.py +65 -0
- agentpool/agents/events/event_emitter.py +3 -0
- agentpool/agents/events/events.py +84 -3
- agentpool/agents/events/infer_info.py +145 -0
- agentpool/agents/events/processors.py +254 -0
- agentpool/agents/interactions.py +41 -6
- agentpool/agents/modes.py +13 -0
- agentpool/agents/slashed_agent.py +5 -4
- agentpool/agents/tool_wrapping.py +18 -6
- agentpool/common_types.py +35 -21
- agentpool/config_resources/acp_assistant.yml +2 -2
- agentpool/config_resources/agents.yml +3 -0
- agentpool/config_resources/agents_template.yml +1 -0
- agentpool/config_resources/claude_code_agent.yml +9 -8
- agentpool/config_resources/external_acp_agents.yml +2 -1
- agentpool/delegation/base_team.py +4 -30
- agentpool/delegation/pool.py +104 -265
- agentpool/delegation/team.py +57 -57
- agentpool/delegation/teamrun.py +50 -55
- agentpool/functional/run.py +10 -4
- agentpool/mcp_server/client.py +73 -38
- agentpool/mcp_server/conversions.py +54 -13
- agentpool/mcp_server/manager.py +9 -23
- agentpool/mcp_server/registries/official_registry_client.py +10 -1
- agentpool/mcp_server/tool_bridge.py +114 -79
- agentpool/messaging/connection_manager.py +11 -10
- agentpool/messaging/event_manager.py +5 -5
- agentpool/messaging/message_container.py +6 -30
- agentpool/messaging/message_history.py +87 -8
- agentpool/messaging/messagenode.py +52 -14
- agentpool/messaging/messages.py +2 -26
- agentpool/messaging/processing.py +10 -22
- agentpool/models/__init__.py +1 -1
- agentpool/models/acp_agents/base.py +6 -2
- agentpool/models/acp_agents/mcp_capable.py +124 -15
- agentpool/models/acp_agents/non_mcp.py +0 -23
- agentpool/models/agents.py +66 -66
- agentpool/models/agui_agents.py +1 -1
- agentpool/models/claude_code_agents.py +111 -17
- agentpool/models/file_parsing.py +0 -1
- agentpool/models/manifest.py +70 -50
- agentpool/prompts/conversion_manager.py +1 -1
- agentpool/prompts/prompts.py +5 -2
- agentpool/resource_providers/__init__.py +2 -0
- agentpool/resource_providers/aggregating.py +4 -2
- agentpool/resource_providers/base.py +13 -3
- agentpool/resource_providers/codemode/code_executor.py +72 -5
- agentpool/resource_providers/codemode/helpers.py +2 -2
- agentpool/resource_providers/codemode/provider.py +64 -12
- agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
- agentpool/resource_providers/codemode/remote_provider.py +9 -12
- agentpool/resource_providers/filtering.py +3 -1
- agentpool/resource_providers/mcp_provider.py +66 -12
- agentpool/resource_providers/plan_provider.py +111 -18
- agentpool/resource_providers/pool.py +5 -3
- agentpool/resource_providers/resource_info.py +111 -0
- agentpool/resource_providers/static.py +2 -2
- agentpool/sessions/__init__.py +2 -0
- agentpool/sessions/manager.py +2 -3
- agentpool/sessions/models.py +9 -6
- agentpool/sessions/protocol.py +28 -0
- agentpool/sessions/session.py +11 -55
- agentpool/storage/manager.py +361 -54
- agentpool/talk/registry.py +4 -4
- agentpool/talk/talk.py +9 -10
- agentpool/testing.py +1 -1
- agentpool/tool_impls/__init__.py +6 -0
- agentpool/tool_impls/agent_cli/__init__.py +42 -0
- agentpool/tool_impls/agent_cli/tool.py +95 -0
- agentpool/tool_impls/bash/__init__.py +64 -0
- agentpool/tool_impls/bash/helpers.py +35 -0
- agentpool/tool_impls/bash/tool.py +171 -0
- agentpool/tool_impls/delete_path/__init__.py +70 -0
- agentpool/tool_impls/delete_path/tool.py +142 -0
- agentpool/tool_impls/download_file/__init__.py +80 -0
- agentpool/tool_impls/download_file/tool.py +183 -0
- agentpool/tool_impls/execute_code/__init__.py +55 -0
- agentpool/tool_impls/execute_code/tool.py +163 -0
- agentpool/tool_impls/grep/__init__.py +80 -0
- agentpool/tool_impls/grep/tool.py +200 -0
- agentpool/tool_impls/list_directory/__init__.py +73 -0
- agentpool/tool_impls/list_directory/tool.py +197 -0
- agentpool/tool_impls/question/__init__.py +42 -0
- agentpool/tool_impls/question/tool.py +127 -0
- agentpool/tool_impls/read/__init__.py +104 -0
- agentpool/tool_impls/read/tool.py +305 -0
- agentpool/tools/__init__.py +2 -1
- agentpool/tools/base.py +114 -34
- agentpool/tools/manager.py +57 -1
- agentpool/ui/base.py +2 -2
- agentpool/ui/mock_provider.py +2 -2
- agentpool/ui/stdlib_provider.py +2 -2
- agentpool/utils/streams.py +21 -96
- agentpool/vfs_registry.py +7 -2
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/METADATA +16 -22
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/RECORD +242 -195
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +20 -0
- agentpool_cli/create.py +1 -1
- agentpool_cli/serve_acp.py +59 -1
- agentpool_cli/serve_opencode.py +1 -1
- agentpool_cli/ui.py +557 -0
- agentpool_commands/__init__.py +12 -5
- agentpool_commands/agents.py +1 -1
- agentpool_commands/pool.py +260 -0
- agentpool_commands/session.py +1 -1
- agentpool_commands/text_sharing/__init__.py +119 -0
- agentpool_commands/text_sharing/base.py +123 -0
- agentpool_commands/text_sharing/github_gist.py +80 -0
- agentpool_commands/text_sharing/opencode.py +462 -0
- agentpool_commands/text_sharing/paste_rs.py +59 -0
- agentpool_commands/text_sharing/pastebin.py +116 -0
- agentpool_commands/text_sharing/shittycodingagent.py +112 -0
- agentpool_commands/utils.py +31 -32
- agentpool_config/__init__.py +30 -2
- agentpool_config/agentpool_tools.py +498 -0
- agentpool_config/converters.py +1 -1
- agentpool_config/event_handlers.py +42 -0
- agentpool_config/events.py +1 -1
- agentpool_config/forward_targets.py +1 -4
- agentpool_config/jinja.py +3 -3
- agentpool_config/mcp_server.py +1 -5
- agentpool_config/nodes.py +1 -1
- agentpool_config/observability.py +44 -0
- agentpool_config/session.py +0 -3
- agentpool_config/storage.py +38 -39
- agentpool_config/task.py +3 -3
- agentpool_config/tools.py +11 -28
- agentpool_config/toolsets.py +22 -90
- agentpool_server/a2a_server/agent_worker.py +307 -0
- agentpool_server/a2a_server/server.py +23 -18
- agentpool_server/acp_server/acp_agent.py +125 -56
- agentpool_server/acp_server/commands/acp_commands.py +46 -216
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +8 -7
- agentpool_server/acp_server/event_converter.py +651 -0
- agentpool_server/acp_server/input_provider.py +53 -10
- agentpool_server/acp_server/server.py +1 -11
- agentpool_server/acp_server/session.py +90 -410
- agentpool_server/acp_server/session_manager.py +8 -34
- agentpool_server/agui_server/server.py +3 -1
- agentpool_server/mcp_server/server.py +5 -2
- agentpool_server/opencode_server/ENDPOINTS.md +53 -14
- agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
- agentpool_server/opencode_server/__init__.py +0 -8
- agentpool_server/opencode_server/converters.py +132 -26
- agentpool_server/opencode_server/input_provider.py +160 -8
- agentpool_server/opencode_server/models/__init__.py +42 -20
- agentpool_server/opencode_server/models/app.py +12 -0
- agentpool_server/opencode_server/models/events.py +203 -29
- agentpool_server/opencode_server/models/mcp.py +19 -0
- agentpool_server/opencode_server/models/message.py +18 -1
- agentpool_server/opencode_server/models/parts.py +134 -1
- agentpool_server/opencode_server/models/question.py +56 -0
- agentpool_server/opencode_server/models/session.py +13 -1
- agentpool_server/opencode_server/routes/__init__.py +4 -0
- agentpool_server/opencode_server/routes/agent_routes.py +33 -2
- agentpool_server/opencode_server/routes/app_routes.py +66 -3
- agentpool_server/opencode_server/routes/config_routes.py +66 -5
- agentpool_server/opencode_server/routes/file_routes.py +184 -5
- agentpool_server/opencode_server/routes/global_routes.py +1 -1
- agentpool_server/opencode_server/routes/lsp_routes.py +1 -1
- agentpool_server/opencode_server/routes/message_routes.py +122 -66
- agentpool_server/opencode_server/routes/permission_routes.py +63 -0
- agentpool_server/opencode_server/routes/pty_routes.py +23 -22
- agentpool_server/opencode_server/routes/question_routes.py +128 -0
- agentpool_server/opencode_server/routes/session_routes.py +139 -68
- agentpool_server/opencode_server/routes/tui_routes.py +1 -1
- agentpool_server/opencode_server/server.py +47 -2
- agentpool_server/opencode_server/state.py +30 -0
- agentpool_storage/__init__.py +0 -4
- agentpool_storage/base.py +81 -2
- agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
- agentpool_storage/claude_provider/__init__.py +42 -0
- agentpool_storage/{claude_provider.py → claude_provider/provider.py} +190 -8
- agentpool_storage/file_provider.py +149 -15
- agentpool_storage/memory_provider.py +132 -12
- agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
- agentpool_storage/opencode_provider/__init__.py +16 -0
- agentpool_storage/opencode_provider/helpers.py +414 -0
- agentpool_storage/opencode_provider/provider.py +895 -0
- agentpool_storage/session_store.py +20 -6
- agentpool_storage/sql_provider/sql_provider.py +135 -2
- agentpool_storage/sql_provider/utils.py +2 -12
- agentpool_storage/zed_provider/__init__.py +16 -0
- agentpool_storage/zed_provider/helpers.py +281 -0
- agentpool_storage/zed_provider/models.py +130 -0
- agentpool_storage/zed_provider/provider.py +442 -0
- agentpool_storage/zed_provider.py +803 -0
- agentpool_toolsets/__init__.py +0 -2
- agentpool_toolsets/builtin/__init__.py +2 -4
- agentpool_toolsets/builtin/code.py +4 -4
- agentpool_toolsets/builtin/debug.py +115 -40
- agentpool_toolsets/builtin/execution_environment.py +54 -165
- agentpool_toolsets/builtin/skills.py +0 -77
- agentpool_toolsets/builtin/subagent_tools.py +64 -51
- agentpool_toolsets/builtin/workers.py +4 -2
- agentpool_toolsets/composio_toolset.py +2 -2
- agentpool_toolsets/entry_points.py +3 -1
- agentpool_toolsets/fsspec_toolset/grep.py +25 -5
- agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
- agentpool_toolsets/fsspec_toolset/toolset.py +350 -66
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +74 -17
- agentpool_toolsets/mcp_run_toolset.py +8 -11
- agentpool_toolsets/notifications.py +33 -33
- agentpool_toolsets/openapi.py +3 -1
- agentpool_toolsets/search_toolset.py +3 -1
- agentpool_config/resources.py +0 -33
- agentpool_server/acp_server/acp_tools.py +0 -43
- agentpool_server/acp_server/commands/spawn.py +0 -210
- agentpool_storage/opencode_provider.py +0 -730
- agentpool_storage/text_log_provider.py +0 -276
- agentpool_toolsets/builtin/chain.py +0 -288
- agentpool_toolsets/builtin/user_interaction.py +0 -52
- agentpool_toolsets/semantic_memory_toolset.py +0 -536
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -14,11 +14,13 @@ import anyio
|
|
|
14
14
|
from exxec.base import ExecutionEnvironment
|
|
15
15
|
from pydantic_ai import (
|
|
16
16
|
BinaryContent,
|
|
17
|
+
ModelResponse,
|
|
17
18
|
PartDeltaEvent,
|
|
18
19
|
PartStartEvent,
|
|
19
20
|
RunContext, # noqa: TC002
|
|
20
21
|
TextPart,
|
|
21
22
|
TextPartDelta,
|
|
23
|
+
ToolCallPart,
|
|
22
24
|
)
|
|
23
25
|
from upathtools import is_directory
|
|
24
26
|
|
|
@@ -26,6 +28,12 @@ from agentpool.agents.context import AgentContext # noqa: TC001
|
|
|
26
28
|
from agentpool.log import get_logger
|
|
27
29
|
from agentpool.mime_utils import guess_type, is_binary_content, is_binary_mime
|
|
28
30
|
from agentpool.resource_providers import ResourceProvider
|
|
31
|
+
from agentpool.tool_impls.delete_path import create_delete_path_tool
|
|
32
|
+
from agentpool.tool_impls.download_file import create_download_file_tool
|
|
33
|
+
from agentpool.tool_impls.grep import create_grep_tool
|
|
34
|
+
from agentpool.tool_impls.list_directory import create_list_directory_tool
|
|
35
|
+
from agentpool.tool_impls.read import create_read_tool
|
|
36
|
+
from agentpool.tools.base import ToolResult # noqa: TC001
|
|
29
37
|
from agentpool_toolsets.builtin.file_edit import replace_content
|
|
30
38
|
from agentpool_toolsets.builtin.file_edit.fuzzy_matcher import StreamingFuzzyMatcher
|
|
31
39
|
from agentpool_toolsets.fsspec_toolset.diagnostics import (
|
|
@@ -47,9 +55,11 @@ from agentpool_toolsets.fsspec_toolset.streaming_diff_parser import (
|
|
|
47
55
|
|
|
48
56
|
|
|
49
57
|
if TYPE_CHECKING:
|
|
58
|
+
from collections.abc import Sequence
|
|
59
|
+
|
|
50
60
|
import fsspec
|
|
51
61
|
from fsspec.asyn import AsyncFileSystem
|
|
52
|
-
from pydantic_ai
|
|
62
|
+
from pydantic_ai import ModelRequest
|
|
53
63
|
|
|
54
64
|
from agentpool.agents.base_agent import BaseAgent
|
|
55
65
|
from agentpool.common_types import ModelType
|
|
@@ -141,7 +151,7 @@ class FSSpecTools(ResourceProvider):
|
|
|
141
151
|
self._max_image_size = max_image_size
|
|
142
152
|
self._max_image_bytes = max_image_bytes
|
|
143
153
|
|
|
144
|
-
def
|
|
154
|
+
def _get_fs(self, agent_ctx: AgentContext) -> AsyncFileSystem:
|
|
145
155
|
"""Get filesystem, falling back to agent's env if not set.
|
|
146
156
|
|
|
147
157
|
Args:
|
|
@@ -197,7 +207,7 @@ class FSSpecTools(ResourceProvider):
|
|
|
197
207
|
# Lazy init repomap - use file's directory as root
|
|
198
208
|
if self._repomap is None:
|
|
199
209
|
root = str(Path(path).parent)
|
|
200
|
-
fs = self.
|
|
210
|
+
fs = self._get_fs(agent_ctx)
|
|
201
211
|
self._repomap = RepoMap(fs, root, max_tokens=self._map_max_tokens)
|
|
202
212
|
|
|
203
213
|
return await self._repomap.get_file_map(path, max_tokens=self._map_max_tokens)
|
|
@@ -222,18 +232,52 @@ class FSSpecTools(ResourceProvider):
|
|
|
222
232
|
return str(Path(cwd) / path)
|
|
223
233
|
return path
|
|
224
234
|
|
|
225
|
-
async def get_tools(self) ->
|
|
235
|
+
async def get_tools(self) -> Sequence[Tool]:
|
|
226
236
|
"""Get filesystem tools."""
|
|
227
237
|
if self._tools is not None:
|
|
228
238
|
return self._tools
|
|
229
239
|
|
|
240
|
+
# Create standalone tools with toolset's configuration
|
|
241
|
+
list_dir_tool = create_list_directory_tool(
|
|
242
|
+
env=self.execution_env,
|
|
243
|
+
cwd=self.cwd,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
read_tool = create_read_tool(
|
|
247
|
+
env=self.execution_env,
|
|
248
|
+
converter=self.converter, # Pass converter for automatic markdown conversion
|
|
249
|
+
cwd=self.cwd,
|
|
250
|
+
max_file_size_kb=self.max_file_size // 1024,
|
|
251
|
+
max_image_size=self._max_image_size,
|
|
252
|
+
max_image_bytes=self._max_image_bytes,
|
|
253
|
+
large_file_tokens=self._large_file_tokens,
|
|
254
|
+
map_max_tokens=self._map_max_tokens,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
grep_tool = create_grep_tool(
|
|
258
|
+
env=self.execution_env,
|
|
259
|
+
cwd=self.cwd,
|
|
260
|
+
max_output_kb=self.max_grep_output // 1024,
|
|
261
|
+
use_subprocess_grep=self.use_subprocess_grep,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
delete_tool = create_delete_path_tool(
|
|
265
|
+
env=self.execution_env,
|
|
266
|
+
cwd=self.cwd,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
download_tool = create_download_file_tool(
|
|
270
|
+
env=self.execution_env,
|
|
271
|
+
cwd=self.cwd,
|
|
272
|
+
)
|
|
273
|
+
|
|
230
274
|
self._tools = [
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
275
|
+
list_dir_tool,
|
|
276
|
+
read_tool,
|
|
277
|
+
grep_tool,
|
|
234
278
|
self.create_tool(self.write, category="edit"),
|
|
235
|
-
|
|
236
|
-
|
|
279
|
+
delete_tool,
|
|
280
|
+
download_tool,
|
|
237
281
|
]
|
|
238
282
|
|
|
239
283
|
# Add edit tool based on config - mutually exclusive
|
|
@@ -246,15 +290,8 @@ class FSSpecTools(ResourceProvider):
|
|
|
246
290
|
else: # simple
|
|
247
291
|
self._tools.append(self.create_tool(self.edit, category="edit"))
|
|
248
292
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
self.create_tool(
|
|
252
|
-
self.read_as_markdown,
|
|
253
|
-
category="read",
|
|
254
|
-
read_only=True,
|
|
255
|
-
idempotent=True,
|
|
256
|
-
)
|
|
257
|
-
)
|
|
293
|
+
# Add regex line editing tool
|
|
294
|
+
self._tools.append(self.create_tool(self.regex_replace_lines, category="edit"))
|
|
258
295
|
|
|
259
296
|
return self._tools
|
|
260
297
|
|
|
@@ -286,7 +323,7 @@ class FSSpecTools(ResourceProvider):
|
|
|
286
323
|
await agent_ctx.events.tool_call_start(title=msg, kind="read", locations=[path])
|
|
287
324
|
|
|
288
325
|
try:
|
|
289
|
-
fs = self.
|
|
326
|
+
fs = self._get_fs(agent_ctx)
|
|
290
327
|
# Check if path exists
|
|
291
328
|
if not await fs._exists(path):
|
|
292
329
|
error_msg = f"Path does not exist: {path}"
|
|
@@ -381,15 +418,18 @@ class FSSpecTools(ResourceProvider):
|
|
|
381
418
|
msg = f"Reading file: {path}"
|
|
382
419
|
from agentpool.agents.events import LocationContentItem
|
|
383
420
|
|
|
421
|
+
# Emit progress - use 0 for line if negative (can't resolve until we read file)
|
|
422
|
+
# LocationContentItem/ToolCallLocation require line >= 0 per ACP spec
|
|
423
|
+
display_line = line if (line is not None and line > 0) else 0
|
|
384
424
|
await agent_ctx.events.tool_call_progress(
|
|
385
425
|
title=msg,
|
|
386
|
-
items=[LocationContentItem(path=path)],
|
|
426
|
+
items=[LocationContentItem(path=path, line=display_line)],
|
|
387
427
|
)
|
|
388
428
|
try:
|
|
389
429
|
mime_type = guess_type(path)
|
|
390
430
|
# Fast path: known binary MIME types (images, audio, video, etc.)
|
|
391
431
|
if is_binary_mime(mime_type):
|
|
392
|
-
data = await self.
|
|
432
|
+
data = await self._get_fs(agent_ctx)._cat_file(path)
|
|
393
433
|
await agent_ctx.events.file_operation("read", path=path, success=True)
|
|
394
434
|
mime = mime_type or "application/octet-stream"
|
|
395
435
|
# Resize images if needed
|
|
@@ -406,7 +446,7 @@ class FSSpecTools(ResourceProvider):
|
|
|
406
446
|
return [note, BinaryContent(data=data, media_type=mime, identifier=path)]
|
|
407
447
|
return BinaryContent(data=data, media_type=mime, identifier=path)
|
|
408
448
|
# Read content and probe for binary (git-style null byte detection)
|
|
409
|
-
data = await self.
|
|
449
|
+
data = await self._get_fs(agent_ctx)._cat_file(path)
|
|
410
450
|
if is_binary_content(data):
|
|
411
451
|
# Binary file - return as BinaryContent for native model handling
|
|
412
452
|
await agent_ctx.events.file_operation("read", path=path, success=True)
|
|
@@ -447,7 +487,11 @@ class FSSpecTools(ResourceProvider):
|
|
|
447
487
|
lines, offset, limit, self.max_file_size
|
|
448
488
|
)
|
|
449
489
|
content = "\n".join(result_lines)
|
|
450
|
-
|
|
490
|
+
# Don't pass negative line numbers to events (ACP requires >= 0)
|
|
491
|
+
display_line = line if (line and line > 0) else 0
|
|
492
|
+
await agent_ctx.events.file_operation(
|
|
493
|
+
"read", path=path, success=True, line=display_line
|
|
494
|
+
)
|
|
451
495
|
if was_truncated:
|
|
452
496
|
content += f"\n\n[Content truncated at {self.max_file_size} bytes]"
|
|
453
497
|
|
|
@@ -458,9 +502,11 @@ class FSSpecTools(ResourceProvider):
|
|
|
458
502
|
# Emit file content for UI display (formatted at ACP layer)
|
|
459
503
|
from agentpool.agents.events import FileContentItem
|
|
460
504
|
|
|
505
|
+
# Use non-negative line for display (negative lines are internal Python convention)
|
|
506
|
+
display_start_line = max(1, line) if line and line > 0 else None
|
|
461
507
|
await agent_ctx.events.tool_call_progress(
|
|
462
508
|
title=f"Read: {path}",
|
|
463
|
-
items=[FileContentItem(content=content, path=path)],
|
|
509
|
+
items=[FileContentItem(content=content, path=path, start_line=display_start_line)],
|
|
464
510
|
replace_content=True,
|
|
465
511
|
)
|
|
466
512
|
# Return raw content for agent
|
|
@@ -504,7 +550,7 @@ class FSSpecTools(ResourceProvider):
|
|
|
504
550
|
content: str,
|
|
505
551
|
mode: str = "w",
|
|
506
552
|
overwrite: bool = False,
|
|
507
|
-
) ->
|
|
553
|
+
) -> str | ToolResult:
|
|
508
554
|
"""Write content to a file.
|
|
509
555
|
|
|
510
556
|
Args:
|
|
@@ -514,8 +560,11 @@ class FSSpecTools(ResourceProvider):
|
|
|
514
560
|
overwrite: Must be True to overwrite existing files (safety check)
|
|
515
561
|
|
|
516
562
|
Returns:
|
|
517
|
-
|
|
563
|
+
Success message or ToolResult with metadata
|
|
518
564
|
"""
|
|
565
|
+
from agentpool.agents.events import DiffContentItem
|
|
566
|
+
from agentpool.tools.base import ToolResult
|
|
567
|
+
|
|
519
568
|
path = self._resolve_path(path, agent_ctx)
|
|
520
569
|
msg = f"Writing file: {path}"
|
|
521
570
|
await agent_ctx.events.tool_call_start(title=msg, kind="edit", locations=[path])
|
|
@@ -526,7 +575,7 @@ class FSSpecTools(ResourceProvider):
|
|
|
526
575
|
if mode not in ("w", "a"):
|
|
527
576
|
msg = f"Invalid mode '{mode}'. Use 'w' (write) or 'a' (append)"
|
|
528
577
|
await agent_ctx.events.file_operation("write", path=path, success=False, error=msg)
|
|
529
|
-
return
|
|
578
|
+
return f"Error: {msg}"
|
|
530
579
|
|
|
531
580
|
# Check size limit
|
|
532
581
|
if content_bytes > self.max_file_size:
|
|
@@ -535,10 +584,10 @@ class FSSpecTools(ResourceProvider):
|
|
|
535
584
|
f"({self.max_file_size} bytes)"
|
|
536
585
|
)
|
|
537
586
|
await agent_ctx.events.file_operation("write", path=path, success=False, error=msg)
|
|
538
|
-
return
|
|
587
|
+
return f"Error: {msg}"
|
|
539
588
|
|
|
540
589
|
# Check if file exists and overwrite protection
|
|
541
|
-
fs = self.
|
|
590
|
+
fs = self._get_fs(agent_ctx)
|
|
542
591
|
file_exists = await fs._exists(path)
|
|
543
592
|
|
|
544
593
|
if file_exists and mode == "w" and not overwrite:
|
|
@@ -547,7 +596,7 @@ class FSSpecTools(ResourceProvider):
|
|
|
547
596
|
f"This is a safety measure to prevent accidental data loss."
|
|
548
597
|
)
|
|
549
598
|
await agent_ctx.events.file_operation("write", path=path, success=False, error=msg)
|
|
550
|
-
return
|
|
599
|
+
return f"Error: {msg}"
|
|
551
600
|
|
|
552
601
|
# Handle append mode: read existing content and prepend it
|
|
553
602
|
if mode == "a" and file_exists:
|
|
@@ -560,23 +609,6 @@ class FSSpecTools(ResourceProvider):
|
|
|
560
609
|
pass # If we can't read, just write new content
|
|
561
610
|
|
|
562
611
|
await self._write(agent_ctx, path, content)
|
|
563
|
-
|
|
564
|
-
try:
|
|
565
|
-
info = await fs._info(path)
|
|
566
|
-
size = info.get("size", content_bytes)
|
|
567
|
-
except (OSError, KeyError):
|
|
568
|
-
size = content_bytes
|
|
569
|
-
|
|
570
|
-
result: dict[str, Any] = {
|
|
571
|
-
"path": path,
|
|
572
|
-
"size": size,
|
|
573
|
-
"mode": mode,
|
|
574
|
-
"file_existed": file_exists,
|
|
575
|
-
"bytes_written": content_bytes,
|
|
576
|
-
}
|
|
577
|
-
# Emit file operation with content for UI display
|
|
578
|
-
from agentpool.agents.events import DiffContentItem
|
|
579
|
-
|
|
580
612
|
await agent_ctx.events.tool_call_progress(
|
|
581
613
|
title=f"Wrote: {path}",
|
|
582
614
|
items=[
|
|
@@ -584,14 +616,40 @@ class FSSpecTools(ResourceProvider):
|
|
|
584
616
|
],
|
|
585
617
|
)
|
|
586
618
|
|
|
587
|
-
# Run diagnostics if enabled
|
|
619
|
+
# Run diagnostics if enabled (include in message for agent)
|
|
620
|
+
diagnostics_msg = ""
|
|
588
621
|
if diagnostics_output := await self._run_diagnostics(agent_ctx, path):
|
|
589
|
-
|
|
622
|
+
diagnostics_msg = f"\n\nDiagnostics:\n{diagnostics_output}"
|
|
623
|
+
|
|
624
|
+
action = "Appended to" if mode == "a" and file_exists else "Wrote"
|
|
625
|
+
success_msg = f"{action} {path} ({content_bytes} bytes){diagnostics_msg}"
|
|
626
|
+
|
|
627
|
+
# TODO: Include diagnostics in metadata for UI display
|
|
628
|
+
# Expected metadata shape:
|
|
629
|
+
# {
|
|
630
|
+
# "diagnostics": {
|
|
631
|
+
# "<file_path>": [
|
|
632
|
+
# {
|
|
633
|
+
# "range": {"start": {"line": 0, "character": 0}, "end": {...}},
|
|
634
|
+
# "message": "...",
|
|
635
|
+
# "severity": 1 # 1=error, 2=warning, 3=info, 4=hint
|
|
636
|
+
# }
|
|
637
|
+
# ]
|
|
638
|
+
# }
|
|
639
|
+
# }
|
|
640
|
+
|
|
641
|
+
return ToolResult(
|
|
642
|
+
content=success_msg, # Agent sees this (includes diagnostics text)
|
|
643
|
+
metadata={
|
|
644
|
+
# Include file content for UI display (used by OpenCode TUI)
|
|
645
|
+
"filePath": str(Path(path).absolute()),
|
|
646
|
+
"content": content,
|
|
647
|
+
# TODO: Add structured diagnostics here for UI
|
|
648
|
+
},
|
|
649
|
+
)
|
|
590
650
|
except Exception as e: # noqa: BLE001
|
|
591
651
|
await agent_ctx.events.file_operation("write", path=path, success=False, error=str(e))
|
|
592
|
-
return
|
|
593
|
-
else:
|
|
594
|
-
return result
|
|
652
|
+
return f"Error: Failed to write file {path}: {e}"
|
|
595
653
|
|
|
596
654
|
async def delete_path( # noqa: D417
|
|
597
655
|
self, agent_ctx: AgentContext, path: str, recursive: bool = False
|
|
@@ -610,7 +668,7 @@ class FSSpecTools(ResourceProvider):
|
|
|
610
668
|
await agent_ctx.events.tool_call_start(title=msg, kind="delete", locations=[path])
|
|
611
669
|
try:
|
|
612
670
|
# Check if path exists and get its type
|
|
613
|
-
fs = self.
|
|
671
|
+
fs = self._get_fs(agent_ctx)
|
|
614
672
|
try:
|
|
615
673
|
info = await fs._info(path)
|
|
616
674
|
path_type = info.get("type", "unknown")
|
|
@@ -668,7 +726,7 @@ class FSSpecTools(ResourceProvider):
|
|
|
668
726
|
description: str,
|
|
669
727
|
replace_all: bool = False,
|
|
670
728
|
line_hint: int | None = None,
|
|
671
|
-
) -> str:
|
|
729
|
+
) -> str | ToolResult:
|
|
672
730
|
r"""Edit a file by replacing specific content with smart matching.
|
|
673
731
|
|
|
674
732
|
Uses sophisticated matching strategies to handle whitespace, indentation,
|
|
@@ -704,7 +762,7 @@ class FSSpecTools(ResourceProvider):
|
|
|
704
762
|
description: str,
|
|
705
763
|
replace_all: bool = False,
|
|
706
764
|
line_hint: int | None = None,
|
|
707
|
-
) -> str:
|
|
765
|
+
) -> str | ToolResult:
|
|
708
766
|
r"""Edit a file by applying multiple replacements in one operation.
|
|
709
767
|
|
|
710
768
|
Uses sophisticated matching strategies to handle whitespace, indentation,
|
|
@@ -718,6 +776,9 @@ class FSSpecTools(ResourceProvider):
|
|
|
718
776
|
Args:
|
|
719
777
|
path: File path (absolute or relative to session cwd)
|
|
720
778
|
replacements: List of (old_string, new_string) tuples to apply sequentially.
|
|
779
|
+
IMPORTANT: Must be a list of pairs, like:
|
|
780
|
+
[("old text", "new text"), ("another old", "another new")]
|
|
781
|
+
|
|
721
782
|
Each old_string should include enough context to uniquely identify
|
|
722
783
|
the target location. For multi-line edits, include the full block.
|
|
723
784
|
description: Human-readable description of what the edit accomplishes
|
|
@@ -746,9 +807,6 @@ class FSSpecTools(ResourceProvider):
|
|
|
746
807
|
if old_str == new_str:
|
|
747
808
|
return f"Error: old_string and new_string must be different: {old_str!r}"
|
|
748
809
|
|
|
749
|
-
# Send initial pending notification
|
|
750
|
-
await agent_ctx.events.file_operation("edit", path=path, success=True)
|
|
751
|
-
|
|
752
810
|
try: # Read current file content
|
|
753
811
|
original_content = await self._read(agent_ctx, path)
|
|
754
812
|
if isinstance(original_content, bytes):
|
|
@@ -790,9 +848,238 @@ class FSSpecTools(ResourceProvider):
|
|
|
790
848
|
error_msg = f"Error editing file: {e}"
|
|
791
849
|
await agent_ctx.events.file_operation("edit", path=path, success=False, error=error_msg)
|
|
792
850
|
return error_msg
|
|
851
|
+
else:
|
|
852
|
+
# Generate unified diff for OpenCode UI
|
|
853
|
+
from difflib import unified_diff
|
|
854
|
+
|
|
855
|
+
from agentpool.tools.base import ToolResult
|
|
856
|
+
|
|
857
|
+
# Ensure content ends with newline for proper diff formatting
|
|
858
|
+
original_for_diff = (
|
|
859
|
+
original_content if original_content.endswith("\n") else original_content + "\n"
|
|
860
|
+
)
|
|
861
|
+
new_for_diff = new_content if new_content.endswith("\n") else new_content + "\n"
|
|
862
|
+
|
|
863
|
+
diff_lines = unified_diff(
|
|
864
|
+
original_for_diff.splitlines(keepends=True),
|
|
865
|
+
new_for_diff.splitlines(keepends=True),
|
|
866
|
+
fromfile=f"a/{Path(path).name}",
|
|
867
|
+
tofile=f"b/{Path(path).name}",
|
|
868
|
+
)
|
|
869
|
+
diff = "".join(diff_lines)
|
|
870
|
+
|
|
871
|
+
# Count additions and deletions
|
|
872
|
+
original_lines = set(original_content.splitlines())
|
|
873
|
+
new_lines = set(new_content.splitlines())
|
|
874
|
+
additions = len(new_lines - original_lines)
|
|
875
|
+
deletions = len(original_lines - new_lines)
|
|
876
|
+
|
|
877
|
+
return ToolResult(
|
|
878
|
+
content=success_msg,
|
|
879
|
+
metadata={
|
|
880
|
+
"diff": diff,
|
|
881
|
+
"filediff": {
|
|
882
|
+
"file": str(Path(path).absolute()),
|
|
883
|
+
"before": original_content,
|
|
884
|
+
"after": new_content,
|
|
885
|
+
"additions": additions,
|
|
886
|
+
"deletions": deletions,
|
|
887
|
+
},
|
|
888
|
+
},
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
async def regex_replace_lines( # noqa: PLR0915
|
|
892
|
+
self,
|
|
893
|
+
agent_ctx: AgentContext,
|
|
894
|
+
path: str,
|
|
895
|
+
start: int | str,
|
|
896
|
+
end: int | str,
|
|
897
|
+
pattern: str,
|
|
898
|
+
replacement: str,
|
|
899
|
+
*,
|
|
900
|
+
count: int = 0,
|
|
901
|
+
) -> str:
|
|
902
|
+
r"""Apply regex replacement to a line range specified by line numbers or text markers.
|
|
903
|
+
|
|
904
|
+
Useful for systematic edits:
|
|
905
|
+
- Remove/add indentation
|
|
906
|
+
- Comment/uncomment blocks
|
|
907
|
+
- Rename variables within scope
|
|
908
|
+
- Delete line ranges
|
|
909
|
+
|
|
910
|
+
Args:
|
|
911
|
+
agent_ctx: Agent execution context
|
|
912
|
+
path: File path to edit
|
|
913
|
+
start: Start of range - int (1-based line number) or str (unique text marker)
|
|
914
|
+
end: End of range - int (1-based line number) or str (first occurrence after start)
|
|
915
|
+
pattern: Regex pattern to search for within the range
|
|
916
|
+
replacement: Replacement string (supports \1, \2 capture groups; empty removes)
|
|
917
|
+
count: Max replacements per line (0 = unlimited)
|
|
918
|
+
|
|
919
|
+
Returns:
|
|
920
|
+
Success message with statistics
|
|
921
|
+
|
|
922
|
+
Examples:
|
|
923
|
+
# Remove a function
|
|
924
|
+
regex_replace_lines(ctx, "file.py", "def old_func(", " return", r".*\n", "")
|
|
925
|
+
|
|
926
|
+
# Indent by line numbers
|
|
927
|
+
regex_replace_lines(ctx, "file.py", 10, 20, r"^", " ")
|
|
928
|
+
|
|
929
|
+
# Uncomment a section
|
|
930
|
+
regex_replace_lines(ctx, "file.py", "# START", "# END", r"^# ", "")
|
|
931
|
+
"""
|
|
932
|
+
import re
|
|
933
|
+
|
|
934
|
+
path = self._resolve_path(path, agent_ctx)
|
|
935
|
+
msg = f"Regex editing file: {path}"
|
|
936
|
+
await agent_ctx.events.tool_call_start(title=msg, kind="edit", locations=[path])
|
|
937
|
+
|
|
938
|
+
try:
|
|
939
|
+
# Read original content
|
|
940
|
+
original_content = await self._read(agent_ctx, path)
|
|
941
|
+
if isinstance(original_content, bytes):
|
|
942
|
+
original_content = original_content.decode("utf-8")
|
|
943
|
+
|
|
944
|
+
lines = original_content.splitlines(keepends=True)
|
|
945
|
+
total_lines = len(lines)
|
|
946
|
+
|
|
947
|
+
# Resolve start position
|
|
948
|
+
if isinstance(start, int):
|
|
949
|
+
if start < 1:
|
|
950
|
+
msg = f"start line must be >= 1, got {start}"
|
|
951
|
+
raise ValueError(msg) # noqa: TRY301
|
|
952
|
+
start_line = start
|
|
953
|
+
else:
|
|
954
|
+
# Find unique occurrence of start string (raises ValueError if not found/unique)
|
|
955
|
+
start_line = self._find_unique_line(lines, start, "start")
|
|
956
|
+
|
|
957
|
+
# Resolve end position
|
|
958
|
+
if isinstance(end, int):
|
|
959
|
+
if end < start_line:
|
|
960
|
+
msg = f"end line {end} must be >= start line {start_line}"
|
|
961
|
+
raise ValueError(msg) # noqa: TRY301
|
|
962
|
+
end_line = end
|
|
963
|
+
else:
|
|
964
|
+
# Find first occurrence of end string after start (raises ValueError if not found)
|
|
965
|
+
end_line = self._find_first_after(lines, end, start_line, "end")
|
|
966
|
+
|
|
967
|
+
# Validate range
|
|
968
|
+
if end_line > total_lines:
|
|
969
|
+
msg = f"end_line {end_line} exceeds file length {total_lines}"
|
|
970
|
+
raise ValueError(msg) # noqa: TRY301
|
|
971
|
+
|
|
972
|
+
# Convert to 0-based indexing for array access
|
|
973
|
+
start_idx = start_line - 1
|
|
974
|
+
end_idx = end_line # end_line is inclusive, but list slice is exclusive
|
|
975
|
+
|
|
976
|
+
# Compile regex pattern
|
|
977
|
+
regex = re.compile(pattern)
|
|
978
|
+
|
|
979
|
+
# Apply replacements to the specified line range
|
|
980
|
+
modified_count = 0
|
|
981
|
+
replacement_count = 0
|
|
982
|
+
|
|
983
|
+
for i in range(start_idx, end_idx):
|
|
984
|
+
original = lines[i]
|
|
985
|
+
modified, num_subs = regex.subn(replacement, original, count=count)
|
|
986
|
+
if num_subs > 0:
|
|
987
|
+
lines[i] = modified
|
|
988
|
+
modified_count += 1
|
|
989
|
+
replacement_count += num_subs
|
|
990
|
+
|
|
991
|
+
# Build new content
|
|
992
|
+
new_content = "".join(lines)
|
|
993
|
+
|
|
994
|
+
# Write back
|
|
995
|
+
await self._write(agent_ctx, path, new_content)
|
|
996
|
+
|
|
997
|
+
# Build success message
|
|
998
|
+
success_msg = (
|
|
999
|
+
f"Successfully applied regex to lines {start_line}-{end_line} in {Path(path).name}"
|
|
1000
|
+
)
|
|
1001
|
+
if modified_count > 0:
|
|
1002
|
+
success_msg += (
|
|
1003
|
+
f" ({modified_count} lines modified, {replacement_count} replacements)"
|
|
1004
|
+
)
|
|
1005
|
+
|
|
1006
|
+
# Emit file edit event for diff display
|
|
1007
|
+
await agent_ctx.events.file_edit_progress(
|
|
1008
|
+
path=path,
|
|
1009
|
+
old_text=original_content,
|
|
1010
|
+
new_text=new_content,
|
|
1011
|
+
status="completed",
|
|
1012
|
+
)
|
|
1013
|
+
|
|
1014
|
+
# Run diagnostics if enabled
|
|
1015
|
+
if diagnostics_output := await self._run_diagnostics(agent_ctx, path):
|
|
1016
|
+
success_msg += f"\n\nDiagnostics:\n{diagnostics_output}"
|
|
1017
|
+
except Exception as e: # noqa: BLE001
|
|
1018
|
+
error_msg = f"Error applying regex to file: {e}"
|
|
1019
|
+
await agent_ctx.events.file_operation("edit", path=path, success=False, error=error_msg)
|
|
1020
|
+
return error_msg
|
|
793
1021
|
else:
|
|
794
1022
|
return success_msg
|
|
795
1023
|
|
|
1024
|
+
@staticmethod
|
|
1025
|
+
def _find_unique_line(lines: list[str], search_text: str, param_name: str) -> int:
|
|
1026
|
+
"""Find unique occurrence of text in lines.
|
|
1027
|
+
|
|
1028
|
+
Args:
|
|
1029
|
+
lines: File lines
|
|
1030
|
+
search_text: Text to search for
|
|
1031
|
+
param_name: Parameter name for error messages
|
|
1032
|
+
|
|
1033
|
+
Returns:
|
|
1034
|
+
Line number (1-based)
|
|
1035
|
+
|
|
1036
|
+
Raises:
|
|
1037
|
+
ValueError: If text not found or matches multiple lines
|
|
1038
|
+
"""
|
|
1039
|
+
matches = []
|
|
1040
|
+
for i, line in enumerate(lines, start=1):
|
|
1041
|
+
if search_text in line:
|
|
1042
|
+
matches.append(i)
|
|
1043
|
+
|
|
1044
|
+
if not matches:
|
|
1045
|
+
msg = f"{param_name} text not found: {search_text!r}"
|
|
1046
|
+
raise ValueError(msg)
|
|
1047
|
+
if len(matches) > 1:
|
|
1048
|
+
match_lines = ", ".join(str(m) for m in matches[:5])
|
|
1049
|
+
more = f" and {len(matches) - 5} more" if len(matches) > 5 else "" # noqa: PLR2004
|
|
1050
|
+
msg = (
|
|
1051
|
+
f"{param_name} text matches multiple lines ({match_lines}{more}). "
|
|
1052
|
+
f"Include more context to make it unique."
|
|
1053
|
+
)
|
|
1054
|
+
raise ValueError(msg)
|
|
1055
|
+
|
|
1056
|
+
return matches[0]
|
|
1057
|
+
|
|
1058
|
+
@staticmethod
|
|
1059
|
+
def _find_first_after(
|
|
1060
|
+
lines: list[str], search_text: str, after_line: int, param_name: str
|
|
1061
|
+
) -> int:
|
|
1062
|
+
"""Find first occurrence of text after a given line.
|
|
1063
|
+
|
|
1064
|
+
Args:
|
|
1065
|
+
lines: File lines
|
|
1066
|
+
search_text: Text to search for
|
|
1067
|
+
after_line: Line number to search after (1-based)
|
|
1068
|
+
param_name: Parameter name for error messages
|
|
1069
|
+
|
|
1070
|
+
Returns:
|
|
1071
|
+
Line number (1-based)
|
|
1072
|
+
|
|
1073
|
+
Raises:
|
|
1074
|
+
ValueError: If text not found after the specified line
|
|
1075
|
+
"""
|
|
1076
|
+
for i in range(after_line - 1, len(lines)):
|
|
1077
|
+
if search_text in lines[i]:
|
|
1078
|
+
return i + 1
|
|
1079
|
+
|
|
1080
|
+
msg = f"{param_name} text not found after line {after_line}: {search_text!r}"
|
|
1081
|
+
raise ValueError(msg)
|
|
1082
|
+
|
|
796
1083
|
async def grep( # noqa: D417
|
|
797
1084
|
self,
|
|
798
1085
|
agent_ctx: AgentContext,
|
|
@@ -855,7 +1142,7 @@ class FSSpecTools(ResourceProvider):
|
|
|
855
1142
|
|
|
856
1143
|
# Fallback to fsspec grep if subprocess didn't work
|
|
857
1144
|
if result is None or "error" in result:
|
|
858
|
-
fs = self.
|
|
1145
|
+
fs = self._get_fs(agent_ctx)
|
|
859
1146
|
result = await grep_with_fsspec(
|
|
860
1147
|
fs=fs,
|
|
861
1148
|
pattern=pattern,
|
|
@@ -898,12 +1185,12 @@ class FSSpecTools(ResourceProvider):
|
|
|
898
1185
|
async def _read(self, agent_ctx: AgentContext, path: str, encoding: str = "utf-8") -> str:
|
|
899
1186
|
# with self.fs.open(path, "r", encoding="utf-8") as f:
|
|
900
1187
|
# return f.read()
|
|
901
|
-
return await self.
|
|
1188
|
+
return await self._get_fs(agent_ctx)._cat(path) # type: ignore[no-any-return]
|
|
902
1189
|
|
|
903
1190
|
async def _write(self, agent_ctx: AgentContext, path: str, content: str | bytes) -> None:
|
|
904
1191
|
if isinstance(content, str):
|
|
905
1192
|
content = content.encode()
|
|
906
|
-
await self.
|
|
1193
|
+
await self._get_fs(agent_ctx)._pipe_file(path, content)
|
|
907
1194
|
|
|
908
1195
|
async def download_file( # noqa: D417
|
|
909
1196
|
self,
|
|
@@ -937,7 +1224,7 @@ class FSSpecTools(ResourceProvider):
|
|
|
937
1224
|
full_path = f"{target_dir.rstrip('/')}/{filename}"
|
|
938
1225
|
|
|
939
1226
|
try:
|
|
940
|
-
fs = self.
|
|
1227
|
+
fs = self._get_fs(agent_ctx)
|
|
941
1228
|
# Ensure target directory exists
|
|
942
1229
|
await fs._makedirs(target_dir, exist_ok=True)
|
|
943
1230
|
|
|
@@ -1000,7 +1287,7 @@ class FSSpecTools(ResourceProvider):
|
|
|
1000
1287
|
await agent_ctx.events.file_operation("read", path=url, success=False, error=error_msg)
|
|
1001
1288
|
return {"error": error_msg}
|
|
1002
1289
|
|
|
1003
|
-
async def agentic_edit( # noqa: D417
|
|
1290
|
+
async def agentic_edit( # noqa: D417
|
|
1004
1291
|
self,
|
|
1005
1292
|
run_ctx: RunContext,
|
|
1006
1293
|
agent_ctx: AgentContext,
|
|
@@ -1063,10 +1350,8 @@ class FSSpecTools(ResourceProvider):
|
|
|
1063
1350
|
# 1. Stored history (previous runs) from agent.conversation
|
|
1064
1351
|
# 2. Current run messages from run_ctx.messages (not yet stored)
|
|
1065
1352
|
stored_history = agent.conversation.get_history()
|
|
1066
|
-
|
|
1067
1353
|
# Build complete message list
|
|
1068
1354
|
all_messages: list[ModelRequest | ModelResponse] = []
|
|
1069
|
-
|
|
1070
1355
|
# Add stored history from previous runs
|
|
1071
1356
|
for chat_msg in stored_history:
|
|
1072
1357
|
all_messages.extend(chat_msg.to_pydantic_ai())
|
|
@@ -1074,7 +1359,6 @@ class FSSpecTools(ResourceProvider):
|
|
|
1074
1359
|
# Add current run's messages (not yet in stored history)
|
|
1075
1360
|
# But exclude the last message if it contains the current agentic_edit tool call
|
|
1076
1361
|
# to avoid the sub-agent seeing "I'm calling agentic_edit" in its context
|
|
1077
|
-
from pydantic_ai.messages import ModelResponse, ToolCallPart
|
|
1078
1362
|
|
|
1079
1363
|
for msg in run_ctx.messages:
|
|
1080
1364
|
if isinstance(msg, ModelResponse):
|
|
Binary file
|