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
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"""Read file tool implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from pydantic_ai import BinaryContent
|
|
10
|
+
|
|
11
|
+
from agentpool.agents.context import AgentContext # noqa: TC001
|
|
12
|
+
from agentpool.log import get_logger
|
|
13
|
+
from agentpool.mime_utils import guess_type, is_binary_content, is_binary_mime
|
|
14
|
+
from agentpool.tools.base import Tool, ToolResult
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Awaitable, Callable
|
|
19
|
+
|
|
20
|
+
from exxec import ExecutionEnvironment
|
|
21
|
+
from fsspec.asyn import AsyncFileSystem
|
|
22
|
+
|
|
23
|
+
from agentpool.prompts.conversion_manager import ConversionManager
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
logger = get_logger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ReadTool(Tool[ToolResult]):
|
|
31
|
+
"""Read files from the filesystem with support for text, binary, and image content.
|
|
32
|
+
|
|
33
|
+
A standalone tool for reading files with advanced features:
|
|
34
|
+
- Automatic binary/text detection
|
|
35
|
+
- Image resizing and compression
|
|
36
|
+
- Large file handling with structure maps
|
|
37
|
+
- Line-based partial reads
|
|
38
|
+
- Multi-encoding support
|
|
39
|
+
|
|
40
|
+
Use create_read_tool() factory for convenient instantiation with defaults.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# Tool-specific configuration
|
|
44
|
+
env: ExecutionEnvironment | None = None
|
|
45
|
+
"""Execution environment to use. Falls back to agent.env if not set."""
|
|
46
|
+
|
|
47
|
+
converter: ConversionManager | None = None
|
|
48
|
+
"""Optional converter for binary files. If set and supports the file type, returns markdown."""
|
|
49
|
+
|
|
50
|
+
cwd: str | None = None
|
|
51
|
+
"""Working directory for resolving relative paths."""
|
|
52
|
+
|
|
53
|
+
max_file_size_kb: int = 64
|
|
54
|
+
"""Maximum file size in KB for read operations."""
|
|
55
|
+
|
|
56
|
+
max_image_size: int | None = 2000
|
|
57
|
+
"""Max width/height for images in pixels. Images are auto-resized if larger."""
|
|
58
|
+
|
|
59
|
+
max_image_bytes: int | None = None
|
|
60
|
+
"""Max file size for images in bytes. Images are compressed if larger."""
|
|
61
|
+
|
|
62
|
+
large_file_tokens: int = 12_000
|
|
63
|
+
"""Token threshold for switching to structure map for large files."""
|
|
64
|
+
|
|
65
|
+
map_max_tokens: int = 2048
|
|
66
|
+
"""Maximum tokens for structure map output."""
|
|
67
|
+
|
|
68
|
+
_repomap = None # RepoMap instance (lazy-init)
|
|
69
|
+
|
|
70
|
+
def get_callable(
|
|
71
|
+
self,
|
|
72
|
+
) -> Callable[..., Awaitable[ToolResult]]:
|
|
73
|
+
"""Return the read method as the callable."""
|
|
74
|
+
return self._read
|
|
75
|
+
|
|
76
|
+
def _get_fs(self, ctx: AgentContext) -> AsyncFileSystem:
|
|
77
|
+
"""Get filesystem from env, falling back to agent's env if not set."""
|
|
78
|
+
from fsspec.asyn import AsyncFileSystem
|
|
79
|
+
from fsspec.implementations.asyn_wrapper import AsyncFileSystemWrapper
|
|
80
|
+
|
|
81
|
+
# Priority: env.get_fs() > agent.env.get_fs()
|
|
82
|
+
if self.env is not None:
|
|
83
|
+
fs = self.env.get_fs()
|
|
84
|
+
return fs if isinstance(fs, AsyncFileSystem) else AsyncFileSystemWrapper(fs)
|
|
85
|
+
fs = ctx.agent.env.get_fs()
|
|
86
|
+
return fs if isinstance(fs, AsyncFileSystem) else AsyncFileSystemWrapper(fs)
|
|
87
|
+
|
|
88
|
+
def _resolve_path(self, path: str, ctx: AgentContext) -> str:
|
|
89
|
+
"""Resolve a potentially relative path to an absolute path."""
|
|
90
|
+
# Get cwd: explicit toolset cwd > env.cwd > agent.env.cwd
|
|
91
|
+
cwd: str | None = None
|
|
92
|
+
if self.cwd:
|
|
93
|
+
cwd = self.cwd
|
|
94
|
+
elif self.env and self.env.cwd:
|
|
95
|
+
cwd = self.env.cwd
|
|
96
|
+
elif ctx.agent.env and ctx.agent.env.cwd:
|
|
97
|
+
cwd = ctx.agent.env.cwd
|
|
98
|
+
|
|
99
|
+
if cwd and not (path.startswith("/") or (len(path) > 1 and path[1] == ":")):
|
|
100
|
+
return str(Path(cwd) / path)
|
|
101
|
+
return path
|
|
102
|
+
|
|
103
|
+
async def _get_file_map(self, path: str, ctx: AgentContext) -> str | None:
|
|
104
|
+
"""Get structure map for a large file if language is supported."""
|
|
105
|
+
from agentpool.repomap import RepoMap, is_language_supported
|
|
106
|
+
|
|
107
|
+
if not is_language_supported(path):
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
# Lazy init repomap - use file's directory as root
|
|
111
|
+
if self._repomap is None:
|
|
112
|
+
root = str(Path(path).parent)
|
|
113
|
+
fs = self._get_fs(ctx)
|
|
114
|
+
self._repomap = RepoMap(fs, root, max_tokens=self.map_max_tokens)
|
|
115
|
+
|
|
116
|
+
return await self._repomap.get_file_map(path, max_tokens=self.map_max_tokens)
|
|
117
|
+
|
|
118
|
+
async def _read( # noqa: PLR0911, PLR0915
|
|
119
|
+
self,
|
|
120
|
+
ctx: AgentContext,
|
|
121
|
+
path: str,
|
|
122
|
+
encoding: str = "utf-8",
|
|
123
|
+
line: int | None = None,
|
|
124
|
+
limit: int | None = None,
|
|
125
|
+
) -> ToolResult:
|
|
126
|
+
"""Read the content of a text file, or use vision capabilities to read images or documents.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
ctx: Agent context for event emission and filesystem access
|
|
130
|
+
path: File path to read
|
|
131
|
+
encoding: Text encoding to use for text files (default: utf-8)
|
|
132
|
+
line: Optional line number to start reading from (1-based, text files only)
|
|
133
|
+
limit: Optional maximum number of lines to read (text files only)
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Text content for text files, BinaryContent for binary files (with optional
|
|
137
|
+
dimension note as list when image was resized), or error string
|
|
138
|
+
"""
|
|
139
|
+
path = self._resolve_path(path, ctx)
|
|
140
|
+
msg = f"Reading file: {path}"
|
|
141
|
+
from agentpool.agents.events import LocationContentItem
|
|
142
|
+
|
|
143
|
+
# Emit progress - use 0 for line if negative (can't resolve until we read file)
|
|
144
|
+
# LocationContentItem/ToolCallLocation require line >= 0 per ACP spec
|
|
145
|
+
display_line = line if (line is not None and line > 0) else 0
|
|
146
|
+
await ctx.events.tool_call_progress(
|
|
147
|
+
title=msg,
|
|
148
|
+
items=[LocationContentItem(path=path, line=display_line)],
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
max_file_size = self.max_file_size_kb * 1024
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
mime_type = guess_type(path)
|
|
155
|
+
# Fast path: known binary MIME types (images, audio, video, etc.)
|
|
156
|
+
if is_binary_mime(mime_type):
|
|
157
|
+
# Try converter first if available
|
|
158
|
+
if self.converter is not None:
|
|
159
|
+
try:
|
|
160
|
+
content = await self.converter.convert_file(path)
|
|
161
|
+
await ctx.events.file_operation("read", path=path, success=True)
|
|
162
|
+
except Exception: # noqa: BLE001
|
|
163
|
+
# Converter doesn't support this file type, fall back to binary
|
|
164
|
+
pass
|
|
165
|
+
else:
|
|
166
|
+
# Converter returned markdown
|
|
167
|
+
lines = content.splitlines()
|
|
168
|
+
preview = "\n".join(lines[:20])
|
|
169
|
+
return ToolResult(
|
|
170
|
+
content=content,
|
|
171
|
+
metadata={"preview": preview, "truncated": False},
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Fall back to native binary handling
|
|
175
|
+
data = await self._get_fs(ctx)._cat_file(path)
|
|
176
|
+
await ctx.events.file_operation("read", path=path, success=True)
|
|
177
|
+
mime = mime_type or "application/octet-stream"
|
|
178
|
+
# Resize images if needed
|
|
179
|
+
if self.max_image_size and mime.startswith("image/"):
|
|
180
|
+
from agentpool_toolsets.fsspec_toolset.image_utils import (
|
|
181
|
+
resize_image_if_needed,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
data, mime, note = resize_image_if_needed(
|
|
185
|
+
data, mime, self.max_image_size, self.max_image_bytes
|
|
186
|
+
)
|
|
187
|
+
if note:
|
|
188
|
+
# Return resized image with dimension note for coordinate mapping
|
|
189
|
+
return ToolResult(
|
|
190
|
+
content=[
|
|
191
|
+
note,
|
|
192
|
+
BinaryContent(data=data, media_type=mime, identifier=path),
|
|
193
|
+
],
|
|
194
|
+
metadata={"preview": "Image read successfully", "truncated": False},
|
|
195
|
+
)
|
|
196
|
+
return ToolResult(
|
|
197
|
+
content=[BinaryContent(data=data, media_type=mime, identifier=path)],
|
|
198
|
+
metadata={"preview": "Binary file read successfully", "truncated": False},
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Read content and probe for binary (git-style null byte detection)
|
|
202
|
+
data = await self._get_fs(ctx)._cat_file(path)
|
|
203
|
+
if is_binary_content(data):
|
|
204
|
+
# Try converter first if available
|
|
205
|
+
if self.converter is not None:
|
|
206
|
+
try:
|
|
207
|
+
content = await self.converter.convert_file(path)
|
|
208
|
+
await ctx.events.file_operation("read", path=path, success=True)
|
|
209
|
+
except Exception: # noqa: BLE001
|
|
210
|
+
# Converter doesn't support this file type, fall back to binary
|
|
211
|
+
pass
|
|
212
|
+
else:
|
|
213
|
+
# Converter returned markdown
|
|
214
|
+
lines = content.splitlines()
|
|
215
|
+
preview = "\n".join(lines[:20])
|
|
216
|
+
return ToolResult(
|
|
217
|
+
content=content,
|
|
218
|
+
metadata={"preview": preview, "truncated": False},
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Fall back to native binary handling
|
|
222
|
+
await ctx.events.file_operation("read", path=path, success=True)
|
|
223
|
+
mime = mime_type or "application/octet-stream"
|
|
224
|
+
# Resize images if needed
|
|
225
|
+
if self.max_image_size and mime.startswith("image/"):
|
|
226
|
+
from agentpool_toolsets.fsspec_toolset.image_utils import (
|
|
227
|
+
resize_image_if_needed,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
data, mime, note = resize_image_if_needed(
|
|
231
|
+
data, mime, self.max_image_size, self.max_image_bytes
|
|
232
|
+
)
|
|
233
|
+
if note:
|
|
234
|
+
return ToolResult(
|
|
235
|
+
content=[
|
|
236
|
+
note,
|
|
237
|
+
BinaryContent(data=data, media_type=mime, identifier=path),
|
|
238
|
+
],
|
|
239
|
+
metadata={"preview": "Image read successfully", "truncated": False},
|
|
240
|
+
)
|
|
241
|
+
return ToolResult(
|
|
242
|
+
content=[BinaryContent(data=data, media_type=mime, identifier=path)],
|
|
243
|
+
metadata={"preview": "Binary file read successfully", "truncated": False},
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
content = data.decode(encoding)
|
|
247
|
+
|
|
248
|
+
# Check if file is too large and no targeted read requested
|
|
249
|
+
tokens_approx = len(content) // 4
|
|
250
|
+
if line is None and limit is None and tokens_approx > self.large_file_tokens:
|
|
251
|
+
# Try structure map for supported languages
|
|
252
|
+
map_result = await self._get_file_map(path, ctx)
|
|
253
|
+
if map_result:
|
|
254
|
+
await ctx.events.file_operation("read", path=path, success=True)
|
|
255
|
+
content = map_result
|
|
256
|
+
else:
|
|
257
|
+
# Fallback: head + tail for unsupported languages
|
|
258
|
+
from agentpool.repomap import truncate_with_notice
|
|
259
|
+
|
|
260
|
+
content = truncate_with_notice(path, content)
|
|
261
|
+
await ctx.events.file_operation("read", path=path, success=True)
|
|
262
|
+
else:
|
|
263
|
+
# Normal read with optional offset/limit
|
|
264
|
+
from agentpool_toolsets.fsspec_toolset.helpers import truncate_lines
|
|
265
|
+
|
|
266
|
+
lines = content.splitlines()
|
|
267
|
+
offset = (line - 1) if line else 0
|
|
268
|
+
result_lines, was_truncated = truncate_lines(lines, offset, limit, max_file_size)
|
|
269
|
+
content = "\n".join(result_lines)
|
|
270
|
+
# Don't pass negative line numbers to events (ACP requires >= 0)
|
|
271
|
+
display_line = line if (line and line > 0) else 0
|
|
272
|
+
await ctx.events.file_operation("read", path=path, success=True, line=display_line)
|
|
273
|
+
if was_truncated:
|
|
274
|
+
content += f"\n\n[Content truncated at {max_file_size} bytes]"
|
|
275
|
+
|
|
276
|
+
except Exception as e: # noqa: BLE001
|
|
277
|
+
await ctx.events.file_operation("read", path=path, success=False, error=str(e))
|
|
278
|
+
error_msg = f"error: Failed to read file {path}: {e}"
|
|
279
|
+
return ToolResult(
|
|
280
|
+
content=error_msg,
|
|
281
|
+
metadata={"preview": "", "truncated": False},
|
|
282
|
+
)
|
|
283
|
+
else:
|
|
284
|
+
# Emit file content for UI display (formatted at ACP layer)
|
|
285
|
+
from agentpool.agents.events import FileContentItem
|
|
286
|
+
|
|
287
|
+
# Use non-negative line for display (negative lines are internal Python convention)
|
|
288
|
+
display_start_line = max(1, line) if line and line > 0 else None
|
|
289
|
+
await ctx.events.tool_call_progress(
|
|
290
|
+
title=f"Read: {path}",
|
|
291
|
+
items=[FileContentItem(content=content, path=path, start_line=display_start_line)],
|
|
292
|
+
replace_content=True,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Prepare metadata for OpenCode UI
|
|
296
|
+
lines = content.splitlines()
|
|
297
|
+
preview = "\n".join(lines[:20])
|
|
298
|
+
# Check if content was truncated
|
|
299
|
+
was_truncated = "[Content truncated" in content
|
|
300
|
+
|
|
301
|
+
# Return result with metadata
|
|
302
|
+
return ToolResult(
|
|
303
|
+
content=content,
|
|
304
|
+
metadata={"preview": preview, "truncated": was_truncated},
|
|
305
|
+
)
|
agentpool/tools/__init__.py
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from agentpool.tools.base import Tool
|
|
5
|
+
from agentpool.tools.base import FunctionTool, Tool
|
|
6
6
|
from agentpool.tools.manager import ToolManager, ToolError
|
|
7
7
|
from agentpool.tools.tool_call_info import ToolCallInfo
|
|
8
8
|
from agentpool.skills.registry import SkillsRegistry
|
|
9
9
|
|
|
10
10
|
__all__ = [
|
|
11
|
+
"FunctionTool",
|
|
11
12
|
"SkillsRegistry",
|
|
12
13
|
"Tool",
|
|
13
14
|
"ToolCallInfo",
|
agentpool/tools/base.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from abc import abstractmethod
|
|
5
6
|
from dataclasses import dataclass, field
|
|
6
7
|
import inspect
|
|
7
8
|
from typing import TYPE_CHECKING, Any, Literal
|
|
@@ -24,12 +25,12 @@ if TYPE_CHECKING:
|
|
|
24
25
|
from collections.abc import Awaitable, Callable
|
|
25
26
|
|
|
26
27
|
from mcp.types import Tool as MCPTool
|
|
28
|
+
from pydantic_ai import UserContent
|
|
27
29
|
from schemez import FunctionSchema, Property
|
|
28
30
|
|
|
29
31
|
from agentpool.common_types import ToolSource
|
|
30
32
|
from agentpool.tools.manager import ToolState
|
|
31
33
|
|
|
32
|
-
|
|
33
34
|
logger = get_logger(__name__)
|
|
34
35
|
ToolKind = Literal[
|
|
35
36
|
"read",
|
|
@@ -46,11 +47,31 @@ ToolKind = Literal[
|
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
@dataclass
|
|
49
|
-
class
|
|
50
|
-
"""
|
|
50
|
+
class ToolResult:
|
|
51
|
+
"""Structured tool result with content for LLM and metadata for UI.
|
|
52
|
+
|
|
53
|
+
This abstraction allows tools to return rich data that gets converted to
|
|
54
|
+
agent-specific formats (pydantic-ai ToolReturn, FastMCP ToolResult, etc.).
|
|
55
|
+
|
|
56
|
+
Attributes:
|
|
57
|
+
content: What the LLM sees - can be string or list of content blocks
|
|
58
|
+
structured_content: Machine-readable JSON data (optional)
|
|
59
|
+
metadata: UI/application data that is NOT sent to the LLM
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
content: str | list[UserContent]
|
|
63
|
+
"""Content sent to the LLM (text, images, etc.)"""
|
|
51
64
|
|
|
52
|
-
|
|
53
|
-
"""
|
|
65
|
+
structured_content: dict[str, Any] | None = None
|
|
66
|
+
"""Structured JSON data for programmatic access (optional)"""
|
|
67
|
+
|
|
68
|
+
metadata: dict[str, Any] | None = None
|
|
69
|
+
"""Metadata for UI/app use - NOT sent to LLM (diffs, diagnostics, etc.)."""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class Tool[TOutputType = Any]:
|
|
74
|
+
"""Base class for tools. Subclass and implement get_callable() or use FunctionTool."""
|
|
54
75
|
|
|
55
76
|
name: str
|
|
56
77
|
"""The name of the tool."""
|
|
@@ -87,14 +108,17 @@ class Tool[TOutputType = Any]:
|
|
|
87
108
|
|
|
88
109
|
__repr__ = dataclasses_no_defaults_repr
|
|
89
110
|
|
|
111
|
+
@abstractmethod
|
|
112
|
+
def get_callable(self) -> Callable[..., TOutputType | Awaitable[TOutputType]]:
|
|
113
|
+
"""Get the callable for this tool. Subclasses must implement."""
|
|
114
|
+
...
|
|
115
|
+
|
|
90
116
|
def to_pydantic_ai(self) -> PydanticAiTool:
|
|
91
117
|
"""Convert tool to Pydantic AI tool."""
|
|
92
118
|
metadata = {**self.metadata, "agent_name": self.agent_name, "category": self.category}
|
|
93
119
|
return PydanticAiTool(
|
|
94
|
-
function=self.
|
|
120
|
+
function=self.get_callable(),
|
|
95
121
|
name=self.name,
|
|
96
|
-
# takes_ctx=self.takes_ctx,
|
|
97
|
-
# max_retries=self.max_retries,
|
|
98
122
|
description=self.description,
|
|
99
123
|
requires_approval=self.requires_confirmation,
|
|
100
124
|
metadata=metadata,
|
|
@@ -108,7 +132,7 @@ class Tool[TOutputType = Any]:
|
|
|
108
132
|
from agentpool.agents.context import AgentContext
|
|
109
133
|
|
|
110
134
|
return schemez.create_schema(
|
|
111
|
-
self.
|
|
135
|
+
self.get_callable(),
|
|
112
136
|
name_override=self.name,
|
|
113
137
|
description_override=self.description,
|
|
114
138
|
exclude_types=[AgentContext, RunContext],
|
|
@@ -165,7 +189,22 @@ class Tool[TOutputType = Any]:
|
|
|
165
189
|
@logfire.instrument("Executing tool {self.name} with args={args}, kwargs={kwargs}")
|
|
166
190
|
async def execute(self, *args: Any, **kwargs: Any) -> Any:
|
|
167
191
|
"""Execute tool, handling both sync and async cases."""
|
|
168
|
-
return await execute(self.
|
|
192
|
+
return await execute(self.get_callable(), *args, **kwargs, use_thread=True)
|
|
193
|
+
|
|
194
|
+
async def execute_and_unwrap(self, *args: Any, **kwargs: Any) -> Any:
|
|
195
|
+
"""Execute tool and unwrap ToolResult if present.
|
|
196
|
+
|
|
197
|
+
This is a convenience method for tests and direct tool usage that want
|
|
198
|
+
plain content instead of ToolResult objects.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
If tool returns ToolResult, returns ToolResult.content.
|
|
202
|
+
Otherwise returns the raw result.
|
|
203
|
+
"""
|
|
204
|
+
result = await self.execute(*args, **kwargs)
|
|
205
|
+
if isinstance(result, ToolResult):
|
|
206
|
+
return result.content
|
|
207
|
+
return result
|
|
169
208
|
|
|
170
209
|
@classmethod
|
|
171
210
|
def from_code(
|
|
@@ -173,15 +212,74 @@ class Tool[TOutputType = Any]:
|
|
|
173
212
|
code: str,
|
|
174
213
|
name: str | None = None,
|
|
175
214
|
description: str | None = None,
|
|
176
|
-
) ->
|
|
177
|
-
"""Create a
|
|
215
|
+
) -> FunctionTool[Any]:
|
|
216
|
+
"""Create a FunctionTool from a code string."""
|
|
178
217
|
namespace: dict[str, Any] = {}
|
|
179
218
|
exec(code, namespace)
|
|
180
219
|
func = next((v for v in namespace.values() if callable(v)), None)
|
|
181
220
|
if not func:
|
|
182
221
|
msg = "No callable found in provided code"
|
|
183
222
|
raise ValueError(msg)
|
|
184
|
-
return
|
|
223
|
+
return FunctionTool.from_callable(
|
|
224
|
+
func, name_override=name, description_override=description
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
@classmethod
|
|
228
|
+
def from_callable(
|
|
229
|
+
cls,
|
|
230
|
+
fn: Callable[..., TOutputType | Awaitable[TOutputType]] | str,
|
|
231
|
+
*,
|
|
232
|
+
name_override: str | None = None,
|
|
233
|
+
description_override: str | None = None,
|
|
234
|
+
schema_override: schemez.OpenAIFunctionDefinition | None = None,
|
|
235
|
+
hints: ToolHints | None = None,
|
|
236
|
+
category: ToolKind | None = None,
|
|
237
|
+
enabled: bool = True,
|
|
238
|
+
source: ToolSource | str | None = None,
|
|
239
|
+
**kwargs: Any,
|
|
240
|
+
) -> FunctionTool[TOutputType]:
|
|
241
|
+
"""Create a FunctionTool from a callable or import path."""
|
|
242
|
+
return FunctionTool.from_callable(
|
|
243
|
+
fn,
|
|
244
|
+
name_override=name_override,
|
|
245
|
+
description_override=description_override,
|
|
246
|
+
schema_override=schema_override,
|
|
247
|
+
hints=hints,
|
|
248
|
+
category=category,
|
|
249
|
+
enabled=enabled,
|
|
250
|
+
source=source,
|
|
251
|
+
**kwargs,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
def to_mcp_tool(self) -> MCPTool:
|
|
255
|
+
"""Convert internal Tool to MCP Tool."""
|
|
256
|
+
schema = self.schema
|
|
257
|
+
from mcp.types import Tool as MCPTool, ToolAnnotations
|
|
258
|
+
|
|
259
|
+
return MCPTool(
|
|
260
|
+
name=schema["function"]["name"],
|
|
261
|
+
description=schema["function"]["description"],
|
|
262
|
+
inputSchema=schema["function"]["parameters"], # pyright: ignore
|
|
263
|
+
annotations=ToolAnnotations(
|
|
264
|
+
title=self.name,
|
|
265
|
+
readOnlyHint=self.hints.read_only if self.hints else None,
|
|
266
|
+
destructiveHint=self.hints.destructive if self.hints else None,
|
|
267
|
+
idempotentHint=self.hints.idempotent if self.hints else None,
|
|
268
|
+
openWorldHint=self.hints.open_world if self.hints else None,
|
|
269
|
+
),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@dataclass
|
|
274
|
+
class FunctionTool[TOutputType = Any](Tool[TOutputType]):
|
|
275
|
+
"""Tool wrapping a plain callable function."""
|
|
276
|
+
|
|
277
|
+
callable: Callable[..., TOutputType | Awaitable[TOutputType]] = field(default=lambda: None) # type: ignore[assignment]
|
|
278
|
+
"""The actual tool implementation."""
|
|
279
|
+
|
|
280
|
+
def get_callable(self) -> Callable[..., TOutputType | Awaitable[TOutputType]]:
|
|
281
|
+
"""Return the wrapped callable."""
|
|
282
|
+
return self.callable
|
|
185
283
|
|
|
186
284
|
@classmethod
|
|
187
285
|
def from_callable(
|
|
@@ -196,14 +294,14 @@ class Tool[TOutputType = Any]:
|
|
|
196
294
|
enabled: bool = True,
|
|
197
295
|
source: ToolSource | str | None = None,
|
|
198
296
|
**kwargs: Any,
|
|
199
|
-
) ->
|
|
297
|
+
) -> FunctionTool[TOutputType]:
|
|
298
|
+
"""Create a FunctionTool from a callable or import path string."""
|
|
200
299
|
if isinstance(fn, str):
|
|
201
300
|
import_path = fn
|
|
202
301
|
from agentpool.utils import importing
|
|
203
302
|
|
|
204
303
|
callable_obj = importing.import_callable(fn)
|
|
205
304
|
name = getattr(callable_obj, "__name__", "unknown")
|
|
206
|
-
import_path = fn
|
|
207
305
|
else:
|
|
208
306
|
callable_obj = fn
|
|
209
307
|
module = fn.__module__
|
|
@@ -215,9 +313,9 @@ class Tool[TOutputType = Any]:
|
|
|
215
313
|
import_path = f"{module}.{fn.__class__.__qualname__}"
|
|
216
314
|
|
|
217
315
|
return cls(
|
|
218
|
-
callable=callable_obj, # pyright: ignore[reportArgumentType]
|
|
219
316
|
name=name_override or name,
|
|
220
317
|
description=description_override or inspect.getdoc(callable_obj) or "",
|
|
318
|
+
callable=callable_obj, # pyright: ignore[reportArgumentType]
|
|
221
319
|
import_path=import_path,
|
|
222
320
|
schema_override=schema_override,
|
|
223
321
|
category=category,
|
|
@@ -227,24 +325,6 @@ class Tool[TOutputType = Any]:
|
|
|
227
325
|
**kwargs,
|
|
228
326
|
)
|
|
229
327
|
|
|
230
|
-
def to_mcp_tool(self) -> MCPTool:
|
|
231
|
-
"""Convert internal Tool to MCP Tool."""
|
|
232
|
-
schema = self.schema
|
|
233
|
-
from mcp.types import Tool as MCPTool, ToolAnnotations
|
|
234
|
-
|
|
235
|
-
return MCPTool(
|
|
236
|
-
name=schema["function"]["name"],
|
|
237
|
-
description=schema["function"]["description"],
|
|
238
|
-
inputSchema=schema["function"]["parameters"], # pyright: ignore
|
|
239
|
-
annotations=ToolAnnotations(
|
|
240
|
-
title=self.name,
|
|
241
|
-
readOnlyHint=self.hints.read_only if self.hints else None,
|
|
242
|
-
destructiveHint=self.hints.destructive if self.hints else None,
|
|
243
|
-
idempotentHint=self.hints.idempotent if self.hints else None,
|
|
244
|
-
openWorldHint=self.hints.open_world if self.hints else None,
|
|
245
|
-
),
|
|
246
|
-
)
|
|
247
|
-
|
|
248
328
|
|
|
249
329
|
@dataclass
|
|
250
330
|
class ToolParameter:
|
agentpool/tools/manager.py
CHANGED
|
@@ -20,6 +20,7 @@ if TYPE_CHECKING:
|
|
|
20
20
|
from agentpool.prompts.prompts import MCPClientPrompt
|
|
21
21
|
from agentpool.resource_providers import ResourceProvider
|
|
22
22
|
from agentpool.resource_providers.codemode.provider import CodeModeResourceProvider
|
|
23
|
+
from agentpool.resource_providers.resource_info import ResourceInfo
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
logger = get_logger(__name__)
|
|
@@ -186,6 +187,8 @@ class ToolManager:
|
|
|
186
187
|
async def list_prompts(self) -> list[MCPClientPrompt]:
|
|
187
188
|
"""Get all prompts from all providers."""
|
|
188
189
|
from agentpool.mcp_server.manager import MCPManager
|
|
190
|
+
from agentpool.prompts.prompts import MCPClientPrompt as MCPPrompt
|
|
191
|
+
from agentpool.resource_providers import AggregatingResourceProvider
|
|
189
192
|
|
|
190
193
|
all_prompts: list[MCPClientPrompt] = []
|
|
191
194
|
# Get prompts from all external providers (check if they're MCP providers)
|
|
@@ -195,12 +198,65 @@ class ToolManager:
|
|
|
195
198
|
# Get prompts from MCP providers via the aggregating provider
|
|
196
199
|
agg_provider = provider.get_aggregating_provider()
|
|
197
200
|
prompts = await agg_provider.get_prompts()
|
|
198
|
-
|
|
201
|
+
# Filter to only MCPClientPrompt instances
|
|
202
|
+
mcp_prompts = [p for p in prompts if isinstance(p, MCPPrompt)]
|
|
203
|
+
all_prompts.extend(mcp_prompts)
|
|
204
|
+
except Exception:
|
|
205
|
+
logger.exception("Failed to get prompts from provider", provider=provider)
|
|
206
|
+
elif isinstance(provider, AggregatingResourceProvider):
|
|
207
|
+
try:
|
|
208
|
+
# AggregatingResourceProvider can directly provide prompts
|
|
209
|
+
prompts = await provider.get_prompts()
|
|
210
|
+
# Filter to only MCPClientPrompt instances
|
|
211
|
+
mcp_prompts = [p for p in prompts if isinstance(p, MCPPrompt)]
|
|
212
|
+
all_prompts.extend(mcp_prompts)
|
|
199
213
|
except Exception:
|
|
200
214
|
logger.exception("Failed to get prompts from provider", provider=provider)
|
|
201
215
|
|
|
202
216
|
return all_prompts
|
|
203
217
|
|
|
218
|
+
async def list_resources(self) -> list[ResourceInfo]:
|
|
219
|
+
"""Get all resources from all providers.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
List of ResourceInfo objects from all providers
|
|
223
|
+
"""
|
|
224
|
+
all_resources: list[ResourceInfo] = []
|
|
225
|
+
# Get resources from all providers concurrently
|
|
226
|
+
provider_coroutines = [provider.get_resources() for provider in self.providers]
|
|
227
|
+
results = await asyncio.gather(*provider_coroutines, return_exceptions=True)
|
|
228
|
+
|
|
229
|
+
for provider, result in zip(self.providers, results, strict=False):
|
|
230
|
+
if isinstance(result, BaseException):
|
|
231
|
+
logger.warning(
|
|
232
|
+
"Failed to get resources from provider",
|
|
233
|
+
provider=provider.name,
|
|
234
|
+
error=str(result),
|
|
235
|
+
)
|
|
236
|
+
continue
|
|
237
|
+
all_resources.extend(result)
|
|
238
|
+
|
|
239
|
+
return all_resources
|
|
240
|
+
|
|
241
|
+
async def get_resource(self, name: str) -> ResourceInfo:
|
|
242
|
+
"""Get a specific resource by name.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
name: Name of the resource to find
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
ResourceInfo for the requested resource
|
|
249
|
+
|
|
250
|
+
Raises:
|
|
251
|
+
ToolError: If resource not found
|
|
252
|
+
"""
|
|
253
|
+
resources = await self.list_resources()
|
|
254
|
+
resource: ResourceInfo | None = next((r for r in resources if r.name == name), None)
|
|
255
|
+
if not resource:
|
|
256
|
+
msg = f"Resource not found: {name}"
|
|
257
|
+
raise ToolError(msg)
|
|
258
|
+
return resource
|
|
259
|
+
|
|
204
260
|
@asynccontextmanager
|
|
205
261
|
async def temporary_tools(
|
|
206
262
|
self,
|
agentpool/ui/base.py
CHANGED
|
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
|
|
|
12
12
|
from mcp import types
|
|
13
13
|
from pydantic import BaseModel
|
|
14
14
|
|
|
15
|
-
from agentpool.agents.context import ConfirmationResult
|
|
15
|
+
from agentpool.agents.context import AgentContext, ConfirmationResult
|
|
16
16
|
from agentpool.messaging import ChatMessage
|
|
17
17
|
from agentpool.messaging.context import NodeContext
|
|
18
18
|
from agentpool.tools.base import Tool
|
|
@@ -62,7 +62,7 @@ class InputProvider(ABC):
|
|
|
62
62
|
@abstractmethod
|
|
63
63
|
def get_tool_confirmation(
|
|
64
64
|
self,
|
|
65
|
-
context:
|
|
65
|
+
context: AgentContext[Any],
|
|
66
66
|
tool: Tool,
|
|
67
67
|
args: dict[str, Any],
|
|
68
68
|
message_history: list[ChatMessage[Any]] | None = None,
|
agentpool/ui/mock_provider.py
CHANGED
|
@@ -11,7 +11,7 @@ from agentpool.ui.base import InputProvider
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from mcp import types
|
|
13
13
|
|
|
14
|
-
from agentpool.agents.context import ConfirmationResult
|
|
14
|
+
from agentpool.agents.context import AgentContext, ConfirmationResult
|
|
15
15
|
from agentpool.messaging import ChatMessage
|
|
16
16
|
from agentpool.messaging.context import NodeContext
|
|
17
17
|
from agentpool.tools.base import Tool
|
|
@@ -58,7 +58,7 @@ class MockInputProvider(InputProvider):
|
|
|
58
58
|
|
|
59
59
|
async def get_tool_confirmation(
|
|
60
60
|
self,
|
|
61
|
-
context:
|
|
61
|
+
context: AgentContext[Any],
|
|
62
62
|
tool: Tool,
|
|
63
63
|
args: dict[str, Any],
|
|
64
64
|
message_history: list[ChatMessage[Any]] | None = None,
|