From 72684b948db6d2fe2a0de03f4eedb3b27794924a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Forr=C3=B3?= Date: Mon, 4 May 2026 21:33:10 +0200 Subject: [PATCH] Make tools cloneable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nikola Forró Assisted-by: Claude Opus 4.6 via Claude Code --- ymir/tools/base.py | 14 ++++++++++++++ ymir/tools/privileged/copr.py | 2 +- ymir/tools/privileged/distgit.py | 3 ++- ymir/tools/privileged/gitlab.py | 2 +- ymir/tools/privileged/jira.py | 2 +- ymir/tools/privileged/lookaside.py | 3 ++- ymir/tools/privileged/zstream_search.py | 3 ++- ymir/tools/pyproject.toml | 3 ++- ymir/tools/unprivileged/commands.py | 3 ++- ymir/tools/unprivileged/distgit_detector.py | 4 +++- ymir/tools/unprivileged/filesystem.py | 3 ++- ymir/tools/unprivileged/greenwave.py | 3 ++- ymir/tools/unprivileged/specfile.py | 2 +- ymir/tools/unprivileged/text.py | 3 ++- ymir/tools/unprivileged/upstream_search.py | 3 ++- ymir/tools/unprivileged/upstream_tools.py | 2 +- ymir/tools/unprivileged/version_mapper.py | 3 ++- ymir/tools/unprivileged/wicked_git.py | 3 ++- 18 files changed, 44 insertions(+), 17 deletions(-) create mode 100644 ymir/tools/base.py diff --git a/ymir/tools/base.py b/ymir/tools/base.py new file mode 100644 index 00000000..71b22c7b --- /dev/null +++ b/ymir/tools/base.py @@ -0,0 +1,14 @@ +import copy +from typing import Self + +from beeai_framework.tools.tool import TInput, Tool, TOutput, TRunOptions + + +class CloneableTool(Tool[TInput, TRunOptions, TOutput]): + async def clone(self) -> Self: + cloned = copy.copy(self) + cloned.middlewares = list(self.middlewares) + cloned._cache = await self.cache.clone() + if self._options is not None: + cloned._options = copy.copy(self._options) + return cloned diff --git a/ymir/tools/privileged/copr.py b/ymir/tools/privileged/copr.py index 6305eb0c..d38b17e3 100644 --- a/ymir/tools/privileged/copr.py +++ b/ymir/tools/privileged/copr.py @@ -13,7 +13,6 @@ from beeai_framework.tools import ( JSONToolOutput, StringToolOutput, - Tool, ToolError, ToolRunOptions, ) @@ -23,6 +22,7 @@ from ymir.common import load_rhel_config from ymir.common.base_utils import KerberosError, init_kerberos_ticket from ymir.common.validators import AbsolutePath +from ymir.tools.base import CloneableTool as Tool from ymir.tools.constants import AIOHTTP_TIMEOUT COPR_CONFIG = { diff --git a/ymir/tools/privileged/distgit.py b/ymir/tools/privileged/distgit.py index 757ef5ee..7daefaac 100644 --- a/ymir/tools/privileged/distgit.py +++ b/ymir/tools/privileged/distgit.py @@ -8,10 +8,11 @@ import koji from beeai_framework.context import RunContext from beeai_framework.emitter import Emitter -from beeai_framework.tools import StringToolOutput, Tool, ToolError, ToolRunOptions +from beeai_framework.tools import StringToolOutput, ToolError, ToolRunOptions from pydantic import BaseModel, Field from ymir.common.base_utils import KerberosError, init_kerberos_ticket +from ymir.tools.base import CloneableTool as Tool from ymir.tools.constants import BREWHUB_URL SYNC_TIMEOUT = 1 * 60 * 60 # seconds diff --git a/ymir/tools/privileged/gitlab.py b/ymir/tools/privileged/gitlab.py index 78df3837..3bc24af8 100644 --- a/ymir/tools/privileged/gitlab.py +++ b/ymir/tools/privileged/gitlab.py @@ -11,7 +11,6 @@ from beeai_framework.tools import ( JSONToolOutput, StringToolOutput, - Tool, ToolError, ToolRunOptions, ) @@ -29,6 +28,7 @@ OpenMergeRequestResult, ) from ymir.common.validators import AbsolutePath +from ymir.tools.base import CloneableTool as Tool from ymir.tools.constants import AIOHTTP_TIMEOUT from ymir.tools.privileged.utils import clean_stale_repositories diff --git a/ymir/tools/privileged/jira.py b/ymir/tools/privileged/jira.py index 85389467..b23781e6 100644 --- a/ymir/tools/privileged/jira.py +++ b/ymir/tools/privileged/jira.py @@ -12,7 +12,6 @@ from beeai_framework.tools import ( JSONToolOutput, StringToolOutput, - Tool, ToolError, ToolRunOptions, ) @@ -22,6 +21,7 @@ from ymir.common.base_utils import get_jira_auth_headers from ymir.common.constants import JIRA_SEARCH_PATH from ymir.common.version_utils import get_fix_version_variants, normalize_fix_version, parse_rhel_version +from ymir.tools.base import CloneableTool as Tool from ymir.tools.constants import AIOHTTP_TIMEOUT if os.getenv("MOCK_JIRA", "False").lower() == "true": diff --git a/ymir/tools/privileged/lookaside.py b/ymir/tools/privileged/lookaside.py index 600e937b..517bf916 100644 --- a/ymir/tools/privileged/lookaside.py +++ b/ymir/tools/privileged/lookaside.py @@ -4,11 +4,12 @@ from beeai_framework.context import RunContext from beeai_framework.emitter import Emitter -from beeai_framework.tools import StringToolOutput, Tool, ToolError, ToolRunOptions +from beeai_framework.tools import StringToolOutput, ToolError, ToolRunOptions from pydantic import BaseModel, Field from ymir.common.base_utils import KerberosError, init_kerberos_ticket, is_cs_branch from ymir.common.validators import AbsolutePath +from ymir.tools.base import CloneableTool as Tool logger = logging.getLogger(__name__) diff --git a/ymir/tools/privileged/zstream_search.py b/ymir/tools/privileged/zstream_search.py index 08e9d683..b5d21dfc 100644 --- a/ymir/tools/privileged/zstream_search.py +++ b/ymir/tools/privileged/zstream_search.py @@ -6,11 +6,12 @@ from beeai_framework.context import RunContext from beeai_framework.emitter import Emitter -from beeai_framework.tools import JSONToolOutput, Tool, ToolError, ToolRunOptions +from beeai_framework.tools import JSONToolOutput, ToolError, ToolRunOptions from pydantic import BaseModel, Field from ymir.common.utils import run_tool from ymir.common.version_utils import is_older_zstream, parse_rhel_version +from ymir.tools.base import CloneableTool as Tool from ymir.tools.privileged.jira import ( GetJiraDevStatusTool, SearchJiraIssuesTool, diff --git a/ymir/tools/pyproject.toml b/ymir/tools/pyproject.toml index 56bedcdd..b58d12e7 100644 --- a/ymir/tools/pyproject.toml +++ b/ymir/tools/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "ymir-tools" -version = "0.3.1" +version = "0.3.2" description = "Ymir MCP tools for AI workflows" requires-python = ">=3.13" dynamic = ["dependencies"] @@ -36,3 +36,4 @@ packages = [] "privileged" = "ymir/tools/privileged" "unprivileged" = "ymir/tools/unprivileged" "constants.py" = "ymir/tools/constants.py" +"base.py" = "ymir/tools/base.py" diff --git a/ymir/tools/unprivileged/commands.py b/ymir/tools/unprivileged/commands.py index 3542bbf5..e3a88ca3 100644 --- a/ymir/tools/unprivileged/commands.py +++ b/ymir/tools/unprivileged/commands.py @@ -6,10 +6,11 @@ from beeai_framework.context import RunContext from beeai_framework.emitter import Emitter -from beeai_framework.tools import JSONToolOutput, Tool, ToolError, ToolRunOptions +from beeai_framework.tools import JSONToolOutput, ToolError, ToolRunOptions from pydantic import BaseModel, Field from ymir.common.base_utils import run_subprocess +from ymir.tools.base import CloneableTool as Tool TIMEOUT = 10 * 60 # seconds ELLIPSIZED_LINES = 200 diff --git a/ymir/tools/unprivileged/distgit_detector.py b/ymir/tools/unprivileged/distgit_detector.py index b1047330..ba1bb19c 100644 --- a/ymir/tools/unprivileged/distgit_detector.py +++ b/ymir/tools/unprivileged/distgit_detector.py @@ -2,9 +2,11 @@ from beeai_framework.context import RunContext from beeai_framework.emitter import Emitter -from beeai_framework.tools import JSONToolOutput, Tool, ToolRunOptions +from beeai_framework.tools import JSONToolOutput, ToolRunOptions from pydantic import BaseModel, Field +from ymir.tools.base import CloneableTool as Tool + class DistgitDetectorInput(BaseModel): url: str = Field(description="URL to check if it's from a dist-git source") diff --git a/ymir/tools/unprivileged/filesystem.py b/ymir/tools/unprivileged/filesystem.py index eccd4d96..58fd2128 100644 --- a/ymir/tools/unprivileged/filesystem.py +++ b/ymir/tools/unprivileged/filesystem.py @@ -3,10 +3,11 @@ from beeai_framework.context import RunContext from beeai_framework.emitter import Emitter -from beeai_framework.tools import StringToolOutput, Tool, ToolError, ToolRunOptions +from beeai_framework.tools import StringToolOutput, ToolError, ToolRunOptions from pydantic import BaseModel, Field from ymir.common.utils import get_absolute_path +from ymir.tools.base import CloneableTool as Tool class GetCWDToolInput(BaseModel): diff --git a/ymir/tools/unprivileged/greenwave.py b/ymir/tools/unprivileged/greenwave.py index c51897ff..f0115ca9 100644 --- a/ymir/tools/unprivileged/greenwave.py +++ b/ymir/tools/unprivileged/greenwave.py @@ -4,9 +4,10 @@ import aiohttp from beeai_framework.context import RunContext from beeai_framework.emitter import Emitter -from beeai_framework.tools import StringToolOutput, Tool, ToolRunOptions +from beeai_framework.tools import StringToolOutput, ToolRunOptions from pydantic import BaseModel, Field +from ymir.tools.base import CloneableTool as Tool from ymir.tools.constants import AIOHTTP_TIMEOUT logger = logging.getLogger(__name__) diff --git a/ymir/tools/unprivileged/specfile.py b/ymir/tools/unprivileged/specfile.py index f48eb7e7..1b72ed59 100644 --- a/ymir/tools/unprivileged/specfile.py +++ b/ymir/tools/unprivileged/specfile.py @@ -8,7 +8,6 @@ from beeai_framework.tools import ( JSONToolOutput, StringToolOutput, - Tool, ToolError, ToolRunOptions, ) @@ -23,6 +22,7 @@ ) from ymir.common.utils import get_absolute_path +from ymir.tools.base import CloneableTool as Tool from ymir.tools.constants import BREWHUB_URL diff --git a/ymir/tools/unprivileged/text.py b/ymir/tools/unprivileged/text.py index eebf2da7..1f5b80aa 100644 --- a/ymir/tools/unprivileged/text.py +++ b/ymir/tools/unprivileged/text.py @@ -4,11 +4,12 @@ from beeai_framework.context import RunContext from beeai_framework.emitter import Emitter -from beeai_framework.tools import StringToolOutput, Tool, ToolError, ToolRunOptions +from beeai_framework.tools import StringToolOutput, ToolError, ToolRunOptions from pydantic import BaseModel, Field from ymir.common.utils import get_absolute_path from ymir.common.validators import NonEmptyString +from ymir.tools.base import CloneableTool as Tool class CreateToolInput(BaseModel): diff --git a/ymir/tools/unprivileged/upstream_search.py b/ymir/tools/unprivileged/upstream_search.py index e305ff39..6dd0a847 100644 --- a/ymir/tools/unprivileged/upstream_search.py +++ b/ymir/tools/unprivileged/upstream_search.py @@ -6,9 +6,10 @@ import aiohttp from beeai_framework.context import RunContext from beeai_framework.emitter import Emitter -from beeai_framework.tools import JSONToolOutput, Tool, ToolError, ToolRunOptions +from beeai_framework.tools import JSONToolOutput, ToolError, ToolRunOptions from pydantic import BaseModel, Field +from ymir.tools.base import CloneableTool as Tool from ymir.tools.constants import AIOHTTP_TIMEOUT logger = logging.getLogger(__name__) diff --git a/ymir/tools/unprivileged/upstream_tools.py b/ymir/tools/unprivileged/upstream_tools.py index 9553e453..957bc34f 100644 --- a/ymir/tools/unprivileged/upstream_tools.py +++ b/ymir/tools/unprivileged/upstream_tools.py @@ -9,7 +9,6 @@ from beeai_framework.tools import ( JSONToolOutput, StringToolOutput, - Tool, ToolError, ToolRunOptions, ) @@ -17,6 +16,7 @@ from ymir.common.base_utils import run_subprocess from ymir.common.validators import AbsolutePath +from ymir.tools.base import CloneableTool as Tool from ymir.tools.constants import AIOHTTP_TIMEOUT diff --git a/ymir/tools/unprivileged/version_mapper.py b/ymir/tools/unprivileged/version_mapper.py index 5d560ba8..c70106ef 100644 --- a/ymir/tools/unprivileged/version_mapper.py +++ b/ymir/tools/unprivileged/version_mapper.py @@ -2,10 +2,11 @@ from beeai_framework.context import RunContext from beeai_framework.emitter import Emitter -from beeai_framework.tools import JSONToolOutput, Tool, ToolRunOptions +from beeai_framework.tools import JSONToolOutput, ToolRunOptions from pydantic import BaseModel, Field from ymir.common.config import load_rhel_config +from ymir.tools.base import CloneableTool as Tool class VersionMapperInput(BaseModel): diff --git a/ymir/tools/unprivileged/wicked_git.py b/ymir/tools/unprivileged/wicked_git.py index 5be21094..b995289e 100644 --- a/ymir/tools/unprivileged/wicked_git.py +++ b/ymir/tools/unprivileged/wicked_git.py @@ -1,10 +1,11 @@ from beeai_framework.context import RunContext from beeai_framework.emitter import Emitter -from beeai_framework.tools import StringToolOutput, Tool, ToolError, ToolRunOptions +from beeai_framework.tools import StringToolOutput, ToolError, ToolRunOptions from pydantic import BaseModel, Field from ymir.common.base_utils import run_subprocess from ymir.common.validators import AbsolutePath +from ymir.tools.base import CloneableTool as Tool class GitPreparePackageSourcesInput(BaseModel):