From c3b4cfd711173410a06b528ecd234b4ced39c95f Mon Sep 17 00:00:00 2001 From: Sriniketh J Date: Wed, 18 Jun 2025 02:21:11 +0530 Subject: [PATCH 01/10] feat: add support for GitHubRepoForkerTool --- .../connectors/github/repo_forker.py | 5 +- .../prompts/github/__init__.py | 3 + .../prompts/github/repo_forker_prompt.py | 61 ++++++++ .../tools/github/__init__.py | 2 + .../tools/github/repo_forker_tool.py | 127 +++++++++++++++++ .../github/tests/test_repo_forker_tool.py | 134 ++++++++++++++++++ 6 files changed, 329 insertions(+), 3 deletions(-) create mode 100644 integrations/github/src/haystack_integrations/prompts/github/repo_forker_prompt.py create mode 100644 integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py create mode 100644 integrations/github/tests/test_repo_forker_tool.py diff --git a/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py b/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py index dc5188b90e..cc008ba9e0 100644 --- a/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py +++ b/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 import re +import time from typing import Any, Dict, Optional import requests @@ -60,7 +61,7 @@ def __init__( :param create_branch: If True, creates a fix branch based on the issue number """ error_message = "github_token must be a Secret" - if not isinstance(github_token, Secret): + if github_token is not None and not isinstance(github_token, Secret): raise TypeError(error_message) self.github_token = github_token @@ -274,8 +275,6 @@ def run(self, url: str) -> dict: # Wait for fork completion if requested if self.wait_for_completion: - import time - start_time = time.time() while time.time() - start_time < self.max_wait_seconds: diff --git a/integrations/github/src/haystack_integrations/prompts/github/__init__.py b/integrations/github/src/haystack_integrations/prompts/github/__init__.py index b13f31c239..95253f5c85 100644 --- a/integrations/github/src/haystack_integrations/prompts/github/__init__.py +++ b/integrations/github/src/haystack_integrations/prompts/github/__init__.py @@ -5,6 +5,7 @@ from .file_editor_prompt import FILE_EDITOR_PROMPT, FILE_EDITOR_SCHEMA from .issue_commenter_prompt import ISSUE_COMMENTER_PROMPT, ISSUE_COMMENTER_SCHEMA from .pr_creator_prompt import PR_CREATOR_PROMPT, PR_CREATOR_SCHEMA +from .repo_forker_prompt import REPO_FORKER_PROMPT, REPO_FORKER_SCHEMA from .repo_viewer_prompt import REPO_VIEWER_PROMPT, REPO_VIEWER_SCHEMA from .system_prompt import SYSTEM_PROMPT @@ -16,6 +17,8 @@ "ISSUE_COMMENTER_SCHEMA", "PR_CREATOR_PROMPT", "PR_CREATOR_SCHEMA", + "REPO_FORKER_PROMPT", + "REPO_FORKER_SCHEMA", "REPO_VIEWER_PROMPT", "REPO_VIEWER_SCHEMA", "SYSTEM_PROMPT", diff --git a/integrations/github/src/haystack_integrations/prompts/github/repo_forker_prompt.py b/integrations/github/src/haystack_integrations/prompts/github/repo_forker_prompt.py new file mode 100644 index 0000000000..68f1c03687 --- /dev/null +++ b/integrations/github/src/haystack_integrations/prompts/github/repo_forker_prompt.py @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: 2023-present deepset GmbH +# +# SPDX-License-Identifier: Apache-2.0 +REPO_FORKER_PROMPT = """Haystack-Agent uses this tool to fork GitHub repositories in order to contribute to issues. +Haystack-Agent initiates a fork so it can freely make changes for contributions. +A fork is required to open a pull request to the upstream repository. +Haystack-Agent works by forking the repository associated with a given issue. + + +Pass a `url` string for the GitHub issue you want to work on in a fork. +It is REQUIRED to pass `url` to use this tool. +The structure must be "https://github.com///issues/". + +Examples: + +- {"url": "https://github.com/deepset-ai/haystack/issues/9343"} + - will fork the "deepset-ai/haystack" repository to work on issue 9343 +- {"url": "https://github.com/deepset-ai/haystack-core-integrations/issues/1685"} + - will fork the "deepset-ai/haystack-core-integrations" repository to work on issue 1685 + + +Haystack-Agent uses the `repo_forker` tool to create a copy (fork) of the target repository into its own account. +Haystack-Agent ensures the issue URL is valid and points to a real GitHub issue. +It parses the URL to identify the correct repository. + + +- Does this issue belong to the repository I need to work on? +- Can I extract the owner and repository name from the URL? +- Why am I forking this repository? (e.g., to implement a fix, to add a feature) +- Is there anything special about the branch or base state I should be aware of? + + +Haystack-Agent reflects on the results after forking: + +- Did the fork succeed? Is the fork visible in my account? +- Can I access, clone, and push to my fork? +- Are there any permissions or fork-specific settings to configure before proceeding? +- Which branch will I be working on in the fork? + + +IMPORTANT +Haystack-Agent ONLY forks the repository mentioned in the given issue URL. +Haystack-Agent does NOT attempt to fork organizations, user profiles, or non-issue URLs. +Haystack-Agent knows that forking is a prerequisite to contributing changes and creating pull requests. + +Haystack-Agent takes notes after the fork: + +- Record the URL of the forked repository +- Note the original issue being worked on +- Document any post-fork steps (e.g., git cloning, installing dependencies) +- Make note of any errors or special setup requirements + +""" + +REPO_FORKER_SCHEMA = { + "properties": { + "url": {"type": "string", "description": "URL of the GitHub issue to work on in the fork."}, + }, + "required": ["url"], + "type": "object", +} diff --git a/integrations/github/src/haystack_integrations/tools/github/__init__.py b/integrations/github/src/haystack_integrations/tools/github/__init__.py index fabeb6acd6..992fb102dc 100644 --- a/integrations/github/src/haystack_integrations/tools/github/__init__.py +++ b/integrations/github/src/haystack_integrations/tools/github/__init__.py @@ -5,6 +5,7 @@ from .issue_commenter_tool import GitHubIssueCommenterTool from .issue_viewer_tool import GitHubIssueViewerTool from .pr_creator_tool import GitHubPRCreatorTool +from .repo_forker_tool import GitHubRepoForkerTool from .repo_viewer_tool import GitHubRepoViewerTool __all__ = [ @@ -12,5 +13,6 @@ "GitHubIssueCommenterTool", "GitHubIssueViewerTool", "GitHubPRCreatorTool", + "GitHubRepoForkerTool", "GitHubRepoViewerTool", ] diff --git a/integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py b/integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py new file mode 100644 index 0000000000..7c421cfe09 --- /dev/null +++ b/integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py @@ -0,0 +1,127 @@ +# SPDX-FileCopyrightText: 2023-present deepset GmbH +# +# SPDX-License-Identifier: Apache-2.0 +from typing import Any, Callable, Dict, Optional, Union + +from haystack.core.serialization import generate_qualified_class_name +from haystack.tools import ComponentTool +from haystack.utils import Secret, deserialize_secrets_inplace + +from haystack_integrations.components.connectors.github.repo_forker import GitHubRepoForker +from haystack_integrations.prompts.github.repo_forker_prompt import REPO_FORKER_PROMPT, REPO_FORKER_SCHEMA +from haystack_integrations.tools.github.utils import deserialize_handlers, serialize_handlers + + +class GitHubRepoForkerTool(ComponentTool): + """ + A tool for forking Github repository. + """ + + def __init__( + self, + *, + name: Optional[str] = "repo_forker", + description: Optional[str] = REPO_FORKER_PROMPT, + parameters: Optional[Dict[str, Any]] = REPO_FORKER_SCHEMA, + github_token: Optional[Secret] = None, + repo: Optional[str] = None, + branch: str = "main", + raise_on_failure: bool = True, + outputs_to_string: Optional[Dict[str, Union[str, Callable[[Any], str]]]] = None, + inputs_from_state: Optional[Dict[str, str]] = None, + outputs_to_state: Optional[Dict[str, Dict[str, Union[str, Callable]]]] = None, + ): + """ + Initialize the GitHub Repo Forker tool. + + :param name: Optional name for the tool. + :param description: Optional description. + :param parameters: Optional JSON schema defining the parameters expected by the Tool. + :param github_token: GitHub personal access token for API authentication + :param repo: Default repository in owner/repo format + :param branch: Default branch to work with + :param raise_on_failure: If True, raises exceptions on API errors + :param outputs_to_string: + Optional dictionary defining how a tool outputs should be converted into a string. + If the source is provided only the specified output key is sent to the handler. + If the source is omitted the whole tool result is sent to the handler. + Example: { + "source": "docs", "handler": format_documents + } + :param inputs_from_state: + Optional dictionary mapping state keys to tool parameter names. + Example: {"repository": "repo"} maps state's "repository" to tool's "repo" parameter. + :param outputs_to_state: + Optional dictionary defining how tool outputs map to keys within state as well as optional handlers. + If the source is provided only the specified output key is sent to the handler. + Example: { + "documents": {"source": "docs", "handler": custom_handler} + } + If the source is omitted the whole tool result is sent to the handler. + Example: { + "documents": {"handler": custom_handler} + } + """ + self.name = name + self.description = description + self.parameters = parameters + self.github_token = github_token + self.repo = repo + self.branch = branch + self.raise_on_failure = raise_on_failure + self.outputs_to_string = outputs_to_string + self.inputs_from_state = inputs_from_state + self.outputs_to_state = outputs_to_state + + repo_forker = GitHubRepoForker( + github_token=github_token, + raise_on_failure=raise_on_failure, + ) + + super().__init__( + component=repo_forker, + name=name, + description=description, + parameters=parameters, + outputs_to_string=self.outputs_to_string, + inputs_from_state=self.inputs_from_state, + outputs_to_state=self.outputs_to_state, + ) + + def to_dict(self) -> Dict[str, Any]: + """ + Serializes the tool to a dictionary. + + Returns: + Dictionary with serialized data. + """ + serialized = { + "name": self.name, + "description": self.description, + "parameters": self.parameters, + "github_token": self.github_token.to_dict() if self.github_token else None, + "repo": self.repo, + "branch": self.branch, + "raise_on_failure": self.raise_on_failure, + "outputs_to_string": self.outputs_to_string, + "inputs_from_state": self.inputs_from_state, + "outputs_to_state": self.outputs_to_state, + } + + serialize_handlers(serialized, self.outputs_to_state, self.outputs_to_string) + return {"type": generate_qualified_class_name(type(self)), "data": serialized} + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "GitHubRepoForkerTool": + """ + Deserializes the tool from a dictionary. + + :param data: + Dictionary to deserialize from. + :returns: + Deserialized tool. + """ + inner_data = data["data"] + deserialize_secrets_inplace(inner_data, keys=["github_token"]) + deserialize_handlers(inner_data) + return cls(**inner_data) diff --git a/integrations/github/tests/test_repo_forker_tool.py b/integrations/github/tests/test_repo_forker_tool.py new file mode 100644 index 0000000000..16bc48ec0c --- /dev/null +++ b/integrations/github/tests/test_repo_forker_tool.py @@ -0,0 +1,134 @@ +# SPDX-FileCopyrightText: 2023-present deepset GmbH +# +# SPDX-License-Identifier: Apache-2.0 +from haystack_integrations.prompts.github.repo_forker_prompt import REPO_FORKER_PROMPT, REPO_FORKER_SCHEMA +from haystack_integrations.tools.github.repo_forker_tool import GitHubRepoForkerTool +from haystack_integrations.tools.github.utils import message_handler + + +class TestGitHubRepoForkerTool: + def test_init(self, monkeypatch): + monkeypatch.setenv("GITHUB_TOKEN", "test-token") + tool = GitHubRepoForkerTool() + + assert tool.name == "repo_forker" + assert tool.description == REPO_FORKER_PROMPT + assert tool.parameters == REPO_FORKER_SCHEMA + assert tool.github_token is None + assert tool.repo is None + assert tool.branch == "main" + assert tool.raise_on_failure is True + assert tool.outputs_to_string is None + assert tool.inputs_from_state is None + assert tool.outputs_to_state is None + + def test_from_dict(self, monkeypatch): + monkeypatch.setenv("GITHUB_TOKEN", "test-token") + tool_dict = { + "type": "haystack_integrations.tools.github.repo_forker_tool.GitHubRepoForkerTool", + "data": { + "name": "repo_forker", + "description": REPO_FORKER_PROMPT, + "parameters": REPO_FORKER_SCHEMA, + "github_token": None, + "repo": None, + "branch": "main", + "raise_on_failure": True, + "outputs_to_string": None, + "inputs_from_state": None, + "outputs_to_state": None, + }, + } + tool = GitHubRepoForkerTool.from_dict(tool_dict) + assert tool.name == "repo_forker" + assert tool.description == REPO_FORKER_PROMPT + assert tool.parameters == REPO_FORKER_SCHEMA + assert tool.github_token is None + assert tool.repo is None + assert tool.branch == "main" + assert tool.raise_on_failure is True + assert tool.outputs_to_string is None + assert tool.inputs_from_state is None + assert tool.outputs_to_state is None + + def test_to_dict(self, monkeypatch): + monkeypatch.setenv("GITHUB_TOKEN", "test-token") + tool = GitHubRepoForkerTool() + tool_dict = tool.to_dict() + assert tool_dict["type"] == "haystack_integrations.tools.github.repo_forker_tool.GitHubRepoForkerTool" + assert tool_dict["data"]["name"] == "repo_forker" + assert tool_dict["data"]["description"] == REPO_FORKER_PROMPT + assert tool_dict["data"]["parameters"] == REPO_FORKER_SCHEMA + assert tool_dict["data"]["github_token"] is None + assert tool_dict["data"]["repo"] is None + assert tool_dict["data"]["branch"] == "main" + assert tool_dict["data"]["raise_on_failure"] is True + assert tool_dict["data"]["outputs_to_string"] is None + assert tool_dict["data"]["inputs_from_state"] is None + assert tool_dict["data"]["outputs_to_state"] is None + + def test_to_dict_with_extra_params(self, monkeypatch): + monkeypatch.setenv("GITHUB_TOKEN", "test-token") + tool = GitHubRepoForkerTool( + github_token=None, + repo="owner/repo", + branch="dev", + raise_on_failure=False, + outputs_to_string={"source": "docs", "handler": message_handler}, + inputs_from_state={"repository": "repo"}, + outputs_to_state={"documents": {"source": "docs", "handler": message_handler}}, + ) + tool_dict = tool.to_dict() + assert tool_dict["type"] == "haystack_integrations.tools.github.repo_forker_tool.GitHubRepoForkerTool" + assert tool_dict["data"]["name"] == "repo_forker" + assert tool_dict["data"]["description"] == REPO_FORKER_PROMPT + assert tool_dict["data"]["parameters"] == REPO_FORKER_SCHEMA + assert tool_dict["data"]["github_token"] is None + assert tool_dict["data"]["repo"] == "owner/repo" + assert tool_dict["data"]["branch"] == "dev" + assert tool_dict["data"]["raise_on_failure"] is False + assert ( + tool_dict["data"]["outputs_to_string"]["handler"] + == "haystack_integrations.tools.github.utils.message_handler" + ) + assert tool_dict["data"]["inputs_from_state"] == {"repository": "repo"} + assert tool_dict["data"]["outputs_to_state"]["documents"]["source"] == "docs" + assert ( + tool_dict["data"]["outputs_to_state"]["documents"]["handler"] + == "haystack_integrations.tools.github.utils.message_handler" + ) + + def test_from_dict_with_extra_params(self, monkeypatch): + monkeypatch.setenv("GITHUB_TOKEN", "test-token") + tool_dict = { + "type": "haystack_integrations.tools.github.repo_forker_tool.GitHubRepoForkerTool", + "data": { + "name": "repo_forker", + "description": REPO_FORKER_PROMPT, + "parameters": REPO_FORKER_SCHEMA, + "github_token": None, + "repo": "owner/repo", + "branch": "dev", + "raise_on_failure": False, + "outputs_to_string": {"handler": "haystack_integrations.tools.github.utils.message_handler"}, + "inputs_from_state": {"repository": "repo"}, + "outputs_to_state": { + "documents": { + "source": "docs", + "handler": "haystack_integrations.tools.github.utils.message_handler", + } + }, + }, + } + tool = GitHubRepoForkerTool.from_dict(tool_dict) + assert tool.name == "repo_forker" + assert tool.description == REPO_FORKER_PROMPT + assert tool.parameters == REPO_FORKER_SCHEMA + assert tool.github_token is None + assert tool.repo == "owner/repo" + assert tool.branch == "dev" + assert tool.raise_on_failure is False + assert tool.outputs_to_string["handler"] == message_handler + assert tool.inputs_from_state == {"repository": "repo"} + assert tool.outputs_to_state["documents"]["source"] == "docs" + assert tool.outputs_to_state["documents"]["handler"] == message_handler From 73859a26fac2970da7205641a513292f17da5a9d Mon Sep 17 00:00:00 2001 From: Sriniketh J Date: Sat, 28 Jun 2025 20:16:09 +0530 Subject: [PATCH 02/10] fix: remove extra params --- .../tools/github/repo_forker_tool.py | 8 -------- .../github/tests/test_repo_forker_tool.py | 16 ---------------- 2 files changed, 24 deletions(-) diff --git a/integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py b/integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py index 7c421cfe09..e641f91f3a 100644 --- a/integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py +++ b/integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py @@ -24,8 +24,6 @@ def __init__( description: Optional[str] = REPO_FORKER_PROMPT, parameters: Optional[Dict[str, Any]] = REPO_FORKER_SCHEMA, github_token: Optional[Secret] = None, - repo: Optional[str] = None, - branch: str = "main", raise_on_failure: bool = True, outputs_to_string: Optional[Dict[str, Union[str, Callable[[Any], str]]]] = None, inputs_from_state: Optional[Dict[str, str]] = None, @@ -38,8 +36,6 @@ def __init__( :param description: Optional description. :param parameters: Optional JSON schema defining the parameters expected by the Tool. :param github_token: GitHub personal access token for API authentication - :param repo: Default repository in owner/repo format - :param branch: Default branch to work with :param raise_on_failure: If True, raises exceptions on API errors :param outputs_to_string: Optional dictionary defining how a tool outputs should be converted into a string. @@ -66,8 +62,6 @@ def __init__( self.description = description self.parameters = parameters self.github_token = github_token - self.repo = repo - self.branch = branch self.raise_on_failure = raise_on_failure self.outputs_to_string = outputs_to_string self.inputs_from_state = inputs_from_state @@ -100,8 +94,6 @@ def to_dict(self) -> Dict[str, Any]: "description": self.description, "parameters": self.parameters, "github_token": self.github_token.to_dict() if self.github_token else None, - "repo": self.repo, - "branch": self.branch, "raise_on_failure": self.raise_on_failure, "outputs_to_string": self.outputs_to_string, "inputs_from_state": self.inputs_from_state, diff --git a/integrations/github/tests/test_repo_forker_tool.py b/integrations/github/tests/test_repo_forker_tool.py index 16bc48ec0c..a60f2a9b2f 100644 --- a/integrations/github/tests/test_repo_forker_tool.py +++ b/integrations/github/tests/test_repo_forker_tool.py @@ -15,8 +15,6 @@ def test_init(self, monkeypatch): assert tool.description == REPO_FORKER_PROMPT assert tool.parameters == REPO_FORKER_SCHEMA assert tool.github_token is None - assert tool.repo is None - assert tool.branch == "main" assert tool.raise_on_failure is True assert tool.outputs_to_string is None assert tool.inputs_from_state is None @@ -31,8 +29,6 @@ def test_from_dict(self, monkeypatch): "description": REPO_FORKER_PROMPT, "parameters": REPO_FORKER_SCHEMA, "github_token": None, - "repo": None, - "branch": "main", "raise_on_failure": True, "outputs_to_string": None, "inputs_from_state": None, @@ -44,8 +40,6 @@ def test_from_dict(self, monkeypatch): assert tool.description == REPO_FORKER_PROMPT assert tool.parameters == REPO_FORKER_SCHEMA assert tool.github_token is None - assert tool.repo is None - assert tool.branch == "main" assert tool.raise_on_failure is True assert tool.outputs_to_string is None assert tool.inputs_from_state is None @@ -60,8 +54,6 @@ def test_to_dict(self, monkeypatch): assert tool_dict["data"]["description"] == REPO_FORKER_PROMPT assert tool_dict["data"]["parameters"] == REPO_FORKER_SCHEMA assert tool_dict["data"]["github_token"] is None - assert tool_dict["data"]["repo"] is None - assert tool_dict["data"]["branch"] == "main" assert tool_dict["data"]["raise_on_failure"] is True assert tool_dict["data"]["outputs_to_string"] is None assert tool_dict["data"]["inputs_from_state"] is None @@ -71,8 +63,6 @@ def test_to_dict_with_extra_params(self, monkeypatch): monkeypatch.setenv("GITHUB_TOKEN", "test-token") tool = GitHubRepoForkerTool( github_token=None, - repo="owner/repo", - branch="dev", raise_on_failure=False, outputs_to_string={"source": "docs", "handler": message_handler}, inputs_from_state={"repository": "repo"}, @@ -84,8 +74,6 @@ def test_to_dict_with_extra_params(self, monkeypatch): assert tool_dict["data"]["description"] == REPO_FORKER_PROMPT assert tool_dict["data"]["parameters"] == REPO_FORKER_SCHEMA assert tool_dict["data"]["github_token"] is None - assert tool_dict["data"]["repo"] == "owner/repo" - assert tool_dict["data"]["branch"] == "dev" assert tool_dict["data"]["raise_on_failure"] is False assert ( tool_dict["data"]["outputs_to_string"]["handler"] @@ -107,8 +95,6 @@ def test_from_dict_with_extra_params(self, monkeypatch): "description": REPO_FORKER_PROMPT, "parameters": REPO_FORKER_SCHEMA, "github_token": None, - "repo": "owner/repo", - "branch": "dev", "raise_on_failure": False, "outputs_to_string": {"handler": "haystack_integrations.tools.github.utils.message_handler"}, "inputs_from_state": {"repository": "repo"}, @@ -125,8 +111,6 @@ def test_from_dict_with_extra_params(self, monkeypatch): assert tool.description == REPO_FORKER_PROMPT assert tool.parameters == REPO_FORKER_SCHEMA assert tool.github_token is None - assert tool.repo == "owner/repo" - assert tool.branch == "dev" assert tool.raise_on_failure is False assert tool.outputs_to_string["handler"] == message_handler assert tool.inputs_from_state == {"repository": "repo"} From d293f27672d4ea322d6205bf1a52f0fdf5c9a66c Mon Sep 17 00:00:00 2001 From: Sriniketh J Date: Thu, 3 Jul 2025 01:15:49 +0530 Subject: [PATCH 03/10] fix: revert token check --- .../components/connectors/github/repo_forker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py b/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py index cc008ba9e0..83f52c652c 100644 --- a/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py +++ b/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py @@ -61,7 +61,7 @@ def __init__( :param create_branch: If True, creates a fix branch based on the issue number """ error_message = "github_token must be a Secret" - if github_token is not None and not isinstance(github_token, Secret): + if not isinstance(github_token, Secret): raise TypeError(error_message) self.github_token = github_token From cd5ff1d99437218e5d444cdbda680c8122fc63cb Mon Sep 17 00:00:00 2001 From: Sriniketh J Date: Thu, 3 Jul 2025 01:21:27 +0530 Subject: [PATCH 04/10] fix: typing issues --- .../components/connectors/github/repo_forker.py | 2 +- .../src/haystack_integrations/tools/github/repo_forker_tool.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py b/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py index 83f52c652c..82a0d7750d 100644 --- a/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py +++ b/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py @@ -41,7 +41,7 @@ class GitHubRepoForker: def __init__( self, *, - github_token: Secret = Secret.from_env_var("GITHUB_TOKEN"), + github_token: Optional[Secret] = None, raise_on_failure: bool = True, wait_for_completion: bool = False, max_wait_seconds: int = 300, diff --git a/integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py b/integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py index e641f91f3a..0b17bc1d29 100644 --- a/integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py +++ b/integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py @@ -58,9 +58,6 @@ def __init__( "documents": {"handler": custom_handler} } """ - self.name = name - self.description = description - self.parameters = parameters self.github_token = github_token self.raise_on_failure = raise_on_failure self.outputs_to_string = outputs_to_string From 47a1c1fc6d56582ff82e70fe439164a912ba0cd2 Mon Sep 17 00:00:00 2001 From: Sriniketh J Date: Sat, 12 Jul 2025 11:54:03 +0530 Subject: [PATCH 05/10] fix: test issue --- .../components/connectors/github/repo_forker.py | 6 +++--- integrations/github/tests/test_repo_forker_tool.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py b/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py index 82a0d7750d..a404d32e3d 100644 --- a/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py +++ b/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py @@ -60,9 +60,9 @@ def __init__( :param auto_sync: If True, syncs fork with original repository if it already exists :param create_branch: If True, creates a fix branch based on the issue number """ - error_message = "github_token must be a Secret" - if not isinstance(github_token, Secret): - raise TypeError(error_message) + if github_token is not None and not isinstance(github_token, Secret): + msg = "github_token must be a Secret" + raise TypeError(msg) self.github_token = github_token self.raise_on_failure = raise_on_failure diff --git a/integrations/github/tests/test_repo_forker_tool.py b/integrations/github/tests/test_repo_forker_tool.py index a60f2a9b2f..8f573b8d3c 100644 --- a/integrations/github/tests/test_repo_forker_tool.py +++ b/integrations/github/tests/test_repo_forker_tool.py @@ -9,8 +9,8 @@ class TestGitHubRepoForkerTool: def test_init(self, monkeypatch): monkeypatch.setenv("GITHUB_TOKEN", "test-token") - tool = GitHubRepoForkerTool() + tool = GitHubRepoForkerTool() assert tool.name == "repo_forker" assert tool.description == REPO_FORKER_PROMPT assert tool.parameters == REPO_FORKER_SCHEMA From 623f566f2bda3e1e1ea5b1bf5cf0fee6fb01bd0b Mon Sep 17 00:00:00 2001 From: Sriniketh J Date: Sat, 19 Jul 2025 19:29:09 +0530 Subject: [PATCH 06/10] fix: revert as per comments --- .../components/connectors/github/repo_forker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py b/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py index a404d32e3d..6773467815 100644 --- a/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py +++ b/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py @@ -60,7 +60,7 @@ def __init__( :param auto_sync: If True, syncs fork with original repository if it already exists :param create_branch: If True, creates a fix branch based on the issue number """ - if github_token is not None and not isinstance(github_token, Secret): + if not isinstance(github_token, Secret): msg = "github_token must be a Secret" raise TypeError(msg) From 651c304aeae05031853af0ea9e5dbb92b749298c Mon Sep 17 00:00:00 2001 From: Sebastian Husch Lee <10526848+sjrl@users.noreply.github.com> Date: Thu, 7 Aug 2025 08:19:59 +0200 Subject: [PATCH 07/10] Update integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py --- .../components/connectors/github/repo_forker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py b/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py index 6773467815..ccea281ae1 100644 --- a/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py +++ b/integrations/github/src/haystack_integrations/components/connectors/github/repo_forker.py @@ -41,7 +41,7 @@ class GitHubRepoForker: def __init__( self, *, - github_token: Optional[Secret] = None, + github_token: Secret = Secret.from_env_var("GITHUB_TOKEN"), raise_on_failure: bool = True, wait_for_completion: bool = False, max_wait_seconds: int = 300, From 38ac22ea6773324ceab94ecc8160fbc3b904ca73 Mon Sep 17 00:00:00 2001 From: Sebastian Husch Lee <10526848+sjrl@users.noreply.github.com> Date: Thu, 7 Aug 2025 08:27:07 +0200 Subject: [PATCH 08/10] Update integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py --- .../src/haystack_integrations/tools/github/repo_forker_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py b/integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py index 0b17bc1d29..e38f4ef1df 100644 --- a/integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py +++ b/integrations/github/src/haystack_integrations/tools/github/repo_forker_tool.py @@ -23,7 +23,7 @@ def __init__( name: Optional[str] = "repo_forker", description: Optional[str] = REPO_FORKER_PROMPT, parameters: Optional[Dict[str, Any]] = REPO_FORKER_SCHEMA, - github_token: Optional[Secret] = None, + github_token: Secret = Secret.from_env_var("GITHUB_TOKEN"), raise_on_failure: bool = True, outputs_to_string: Optional[Dict[str, Union[str, Callable[[Any], str]]]] = None, inputs_from_state: Optional[Dict[str, str]] = None, From 5b975c362e24fd05fef9e26171e4a7668ee6b3ab Mon Sep 17 00:00:00 2001 From: Sriniketh J Date: Fri, 15 Aug 2025 23:23:19 +0530 Subject: [PATCH 09/10] fix: test failures --- .../github/tests/test_repo_forker_tool.py | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/integrations/github/tests/test_repo_forker_tool.py b/integrations/github/tests/test_repo_forker_tool.py index 8f573b8d3c..818d09dd6f 100644 --- a/integrations/github/tests/test_repo_forker_tool.py +++ b/integrations/github/tests/test_repo_forker_tool.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: 2023-present deepset GmbH # # SPDX-License-Identifier: Apache-2.0 +from haystack.utils import Secret + from haystack_integrations.prompts.github.repo_forker_prompt import REPO_FORKER_PROMPT, REPO_FORKER_SCHEMA from haystack_integrations.tools.github.repo_forker_tool import GitHubRepoForkerTool from haystack_integrations.tools.github.utils import message_handler @@ -14,7 +16,7 @@ def test_init(self, monkeypatch): assert tool.name == "repo_forker" assert tool.description == REPO_FORKER_PROMPT assert tool.parameters == REPO_FORKER_SCHEMA - assert tool.github_token is None + assert tool.github_token == Secret.from_env_var("GITHUB_TOKEN") assert tool.raise_on_failure is True assert tool.outputs_to_string is None assert tool.inputs_from_state is None @@ -28,7 +30,7 @@ def test_from_dict(self, monkeypatch): "name": "repo_forker", "description": REPO_FORKER_PROMPT, "parameters": REPO_FORKER_SCHEMA, - "github_token": None, + "github_token": {"env_vars": ["GITHUB_TOKEN"], "strict": True, "type": "env_var"}, "raise_on_failure": True, "outputs_to_string": None, "inputs_from_state": None, @@ -39,7 +41,7 @@ def test_from_dict(self, monkeypatch): assert tool.name == "repo_forker" assert tool.description == REPO_FORKER_PROMPT assert tool.parameters == REPO_FORKER_SCHEMA - assert tool.github_token is None + assert tool.github_token == Secret.from_env_var("GITHUB_TOKEN") assert tool.raise_on_failure is True assert tool.outputs_to_string is None assert tool.inputs_from_state is None @@ -53,7 +55,11 @@ def test_to_dict(self, monkeypatch): assert tool_dict["data"]["name"] == "repo_forker" assert tool_dict["data"]["description"] == REPO_FORKER_PROMPT assert tool_dict["data"]["parameters"] == REPO_FORKER_SCHEMA - assert tool_dict["data"]["github_token"] is None + assert tool_dict["data"]["github_token"] == { + "env_vars": ["GITHUB_TOKEN"], + "strict": True, + "type": "env_var", + } assert tool_dict["data"]["raise_on_failure"] is True assert tool_dict["data"]["outputs_to_string"] is None assert tool_dict["data"]["inputs_from_state"] is None @@ -62,7 +68,7 @@ def test_to_dict(self, monkeypatch): def test_to_dict_with_extra_params(self, monkeypatch): monkeypatch.setenv("GITHUB_TOKEN", "test-token") tool = GitHubRepoForkerTool( - github_token=None, + github_token=Secret.from_env_var("GITHUB_TOKEN"), raise_on_failure=False, outputs_to_string={"source": "docs", "handler": message_handler}, inputs_from_state={"repository": "repo"}, @@ -73,7 +79,11 @@ def test_to_dict_with_extra_params(self, monkeypatch): assert tool_dict["data"]["name"] == "repo_forker" assert tool_dict["data"]["description"] == REPO_FORKER_PROMPT assert tool_dict["data"]["parameters"] == REPO_FORKER_SCHEMA - assert tool_dict["data"]["github_token"] is None + assert tool_dict["data"]["github_token"] == { + "env_vars": ["GITHUB_TOKEN"], + "strict": True, + "type": "env_var", + } assert tool_dict["data"]["raise_on_failure"] is False assert ( tool_dict["data"]["outputs_to_string"]["handler"] @@ -94,7 +104,7 @@ def test_from_dict_with_extra_params(self, monkeypatch): "name": "repo_forker", "description": REPO_FORKER_PROMPT, "parameters": REPO_FORKER_SCHEMA, - "github_token": None, + "github_token": {"env_vars": ["GITHUB_TOKEN"], "strict": True, "type": "env_var"}, "raise_on_failure": False, "outputs_to_string": {"handler": "haystack_integrations.tools.github.utils.message_handler"}, "inputs_from_state": {"repository": "repo"}, @@ -110,7 +120,7 @@ def test_from_dict_with_extra_params(self, monkeypatch): assert tool.name == "repo_forker" assert tool.description == REPO_FORKER_PROMPT assert tool.parameters == REPO_FORKER_SCHEMA - assert tool.github_token is None + assert tool.github_token == Secret.from_env_var("GITHUB_TOKEN") assert tool.raise_on_failure is False assert tool.outputs_to_string["handler"] == message_handler assert tool.inputs_from_state == {"repository": "repo"} From 78cf9d210c51992cbe094714766e35659c55f093 Mon Sep 17 00:00:00 2001 From: Sriniketh J Date: Fri, 15 Aug 2025 23:25:03 +0530 Subject: [PATCH 10/10] fix: formatting issue --- integrations/github/tests/test_repo_forker_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/github/tests/test_repo_forker_tool.py b/integrations/github/tests/test_repo_forker_tool.py index 818d09dd6f..06f9859200 100644 --- a/integrations/github/tests/test_repo_forker_tool.py +++ b/integrations/github/tests/test_repo_forker_tool.py @@ -55,7 +55,7 @@ def test_to_dict(self, monkeypatch): assert tool_dict["data"]["name"] == "repo_forker" assert tool_dict["data"]["description"] == REPO_FORKER_PROMPT assert tool_dict["data"]["parameters"] == REPO_FORKER_SCHEMA - assert tool_dict["data"]["github_token"] == { + assert tool_dict["data"]["github_token"] == { "env_vars": ["GITHUB_TOKEN"], "strict": True, "type": "env_var",