github-haystack 1.1.0__tar.gz → 1.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. github_haystack-1.2.1/CHANGELOG.md +50 -0
  2. {github_haystack-1.1.0 → github_haystack-1.2.1}/PKG-INFO +7 -16
  3. github_haystack-1.2.1/README.md +12 -0
  4. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/components/connectors/github/file_editor.py +4 -2
  5. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/components/connectors/github/issue_commenter.py +3 -1
  6. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/components/connectors/github/issue_viewer.py +3 -1
  7. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/components/connectors/github/pr_creator.py +3 -1
  8. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/components/connectors/github/repo_forker.py +5 -3
  9. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/components/connectors/github/repo_viewer.py +3 -1
  10. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/__init__.py +3 -0
  11. github_haystack-1.2.1/src/haystack_integrations/prompts/github/repo_forker_prompt.py +61 -0
  12. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/github/__init__.py +2 -0
  13. github_haystack-1.2.1/src/haystack_integrations/tools/github/repo_forker_tool.py +116 -0
  14. {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_file_editor.py +11 -0
  15. {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_file_editor_tool.py +9 -7
  16. {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_issue_commenter.py +11 -0
  17. {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_issue_viewer.py +34 -0
  18. {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_pr_creator.py +11 -0
  19. {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_repo_forker.py +12 -1
  20. github_haystack-1.2.1/tests/test_repo_forker_tool.py +128 -0
  21. {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_repo_viewer.py +12 -0
  22. github_haystack-1.1.0/CHANGELOG.md +0 -20
  23. github_haystack-1.1.0/README.md +0 -21
  24. {github_haystack-1.1.0 → github_haystack-1.2.1}/.gitignore +0 -0
  25. {github_haystack-1.1.0 → github_haystack-1.2.1}/LICENSE.txt +0 -0
  26. {github_haystack-1.1.0 → github_haystack-1.2.1}/pydoc/config.yml +0 -0
  27. {github_haystack-1.1.0 → github_haystack-1.2.1}/pyproject.toml +0 -0
  28. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/components/connectors/github/__init__.py +0 -0
  29. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/components/connectors/py.typed +0 -0
  30. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/context_prompt.py +0 -0
  31. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/file_editor_prompt.py +0 -0
  32. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/issue_commenter_prompt.py +0 -0
  33. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/issue_viewer_prompt.py +0 -0
  34. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/pr_creator_prompt.py +0 -0
  35. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/repo_viewer_prompt.py +0 -0
  36. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/github/system_prompt.py +0 -0
  37. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/prompts/py.typed +0 -0
  38. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/github/file_editor_tool.py +0 -0
  39. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/github/issue_commenter_tool.py +0 -0
  40. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/github/issue_viewer_tool.py +0 -0
  41. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/github/pr_creator_tool.py +0 -0
  42. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/github/repo_viewer_tool.py +0 -0
  43. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/github/utils.py +0 -0
  44. {github_haystack-1.1.0 → github_haystack-1.2.1}/src/haystack_integrations/tools/py.typed +0 -0
  45. {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/__init__.py +0 -0
  46. {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_issue_commenter_tool.py +0 -0
  47. {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_issue_viewer_tool.py +0 -0
  48. {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_pr_creator_tool.py +0 -0
  49. {github_haystack-1.1.0 → github_haystack-1.2.1}/tests/test_repo_viewer_tool.py +0 -0
@@ -0,0 +1,50 @@
1
+ # Changelog
2
+
3
+ ## [integrations/github-v1.2.0] - 2025-10-13
4
+
5
+ ### 🚀 Features
6
+
7
+ - Add support for GitHubRepoForkerTool (#1968)
8
+
9
+ ### 🧪 Testing
10
+
11
+ - Fix GitHub test (#2119)
12
+
13
+ ### 🧹 Chores
14
+
15
+ - Standardize readmes - part 2 (#2205)
16
+
17
+ ### 🌀 Miscellaneous
18
+
19
+ - Chore: Fix linting github (#2255)
20
+
21
+ ## [integrations/github-v1.1.0] - 2025-07-01
22
+
23
+ ### 🐛 Bug Fixes
24
+
25
+ - Fix Github types + add py.typed (#2024)
26
+
27
+ ### 🧹 Chores
28
+
29
+ - Fix linting for ruff 0.12.0 (#1969)
30
+ - Remove black (#1985)
31
+
32
+
33
+ ## [integrations/github-v1.0.1] - 2025-06-12
34
+
35
+ ### 🐛 Bug Fixes
36
+
37
+ - Prompts and schemas of GitHubIssueCommenterTool and GitHubIssueViewerTool now properly explain url parameter (#1929)
38
+
39
+
40
+ ### 🧹 Chores
41
+
42
+ - Align core-integrations Hatch scripts (#1898)
43
+
44
+ ## [integrations/github-v1.0.0] - 2025-05-28
45
+
46
+ ### 🚀 Features
47
+
48
+ - Add GitHub integration with components, tools, and prompts (#1637)
49
+
50
+ <!-- generated by git-cliff -->
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github-haystack
3
- Version: 1.1.0
3
+ Version: 1.2.1
4
4
  Summary: Haystack components for interacting with GitHub repositories
5
5
  Project-URL: Source, https://github.com/deepset-ai/haystack-core-integrations/github
6
6
  Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/github/README.md
@@ -27,19 +27,10 @@ Description-Content-Type: text/markdown
27
27
  [![PyPI - Version](https://img.shields.io/pypi/v/github-haystack.svg)](https://pypi.org/project/github-haystack)
28
28
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/github-haystack.svg)](https://pypi.org/project/github-haystack)
29
29
 
30
- -----
30
+ - [Integration page](https://haystack.deepset.ai/integrations/github)
31
+ - [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/github/CHANGELOG.md)
32
+
33
+ ---
34
+ ## Contributing
31
35
 
32
- ## Table of Contents
33
-
34
- - [Installation](#installation)
35
- - [License](#license)
36
-
37
- ## Installation
38
-
39
- ```console
40
- pip install github-haystack
41
- ```
42
-
43
- ## License
44
-
45
- `github-haystack` is distributed under the terms of the [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) license.
36
+ Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
@@ -0,0 +1,12 @@
1
+ # github-haystack
2
+
3
+ [![PyPI - Version](https://img.shields.io/pypi/v/github-haystack.svg)](https://pypi.org/project/github-haystack)
4
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/github-haystack.svg)](https://pypi.org/project/github-haystack)
5
+
6
+ - [Integration page](https://haystack.deepset.ai/integrations/github)
7
+ - [Changelog](https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/github/CHANGELOG.md)
8
+
9
+ ---
10
+ ## Contributing
11
+
12
+ Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
@@ -115,7 +115,9 @@ class GitHubFileEditor:
115
115
  """
116
116
  headers = self.base_headers.copy()
117
117
  if self.github_token is not None:
118
- headers["Authorization"] = f"Bearer {self.github_token.resolve_value()}"
118
+ token_value = self.github_token.resolve_value()
119
+ if token_value:
120
+ headers["Authorization"] = f"Bearer {token_value}"
119
121
  return headers
120
122
 
121
123
  def _get_file_content(self, owner: str, repo: str, path: str, branch: str) -> tuple[str, str]:
@@ -225,7 +227,7 @@ class GitHubFileEditor:
225
227
  def _delete_file(self, owner: str, repo: str, payload: Dict[str, str], branch: str) -> str:
226
228
  """Handle file deletion."""
227
229
  try:
228
- content, sha = self._get_file_content(owner, repo, payload["path"], branch)
230
+ _, sha = self._get_file_content(owner, repo, payload["path"], branch)
229
231
  url = f"https://api.github.com/repos/{owner}/{repo}/contents/{payload['path']}"
230
232
 
231
233
  data = {"message": payload["message"], "sha": sha, "branch": branch}
@@ -67,7 +67,9 @@ class GitHubIssueCommenter:
67
67
  """
68
68
  headers = self.base_headers.copy()
69
69
  if self.github_token is not None:
70
- headers["Authorization"] = f"Bearer {self.github_token.resolve_value()}"
70
+ token_value = self.github_token.resolve_value()
71
+ if token_value:
72
+ headers["Authorization"] = f"Bearer {token_value}"
71
73
  return headers
72
74
 
73
75
  def _parse_github_url(self, url: str) -> tuple[str, str, int]:
@@ -66,7 +66,9 @@ class GitHubIssueViewer:
66
66
  """
67
67
  headers = self.base_headers.copy()
68
68
  if self.github_token:
69
- headers["Authorization"] = f"Bearer {self.github_token.resolve_value()}"
69
+ token_value = self.github_token.resolve_value()
70
+ if token_value:
71
+ headers["Authorization"] = f"Bearer {token_value}"
70
72
  return headers
71
73
 
72
74
  def _parse_github_url(self, url: str) -> tuple[str, str, int]:
@@ -65,7 +65,9 @@ class GitHubPRCreator:
65
65
  """
66
66
  headers = self.base_headers.copy()
67
67
  if self.github_token is not None:
68
- headers["Authorization"] = f"Bearer {self.github_token.resolve_value()}"
68
+ token_value = self.github_token.resolve_value()
69
+ if token_value:
70
+ headers["Authorization"] = f"Bearer {token_value}"
69
71
  return headers
70
72
 
71
73
  def _parse_issue_url(self, issue_url: str) -> tuple[str, str, str]:
@@ -60,9 +60,9 @@ class GitHubRepoForker:
60
60
  :param auto_sync: If True, syncs fork with original repository if it already exists
61
61
  :param create_branch: If True, creates a fix branch based on the issue number
62
62
  """
63
- error_message = "github_token must be a Secret"
64
63
  if not isinstance(github_token, Secret):
65
- raise TypeError(error_message)
64
+ msg = "github_token must be a Secret"
65
+ raise TypeError(msg)
66
66
 
67
67
  self.github_token = github_token
68
68
  self.raise_on_failure = raise_on_failure
@@ -85,7 +85,9 @@ class GitHubRepoForker:
85
85
  """
86
86
  headers = self.base_headers.copy()
87
87
  if self.github_token is not None:
88
- headers["Authorization"] = f"Bearer {self.github_token.resolve_value()}"
88
+ token_value = self.github_token.resolve_value()
89
+ if token_value:
90
+ headers["Authorization"] = f"Bearer {token_value}"
89
91
  return headers
90
92
 
91
93
  def _parse_github_url(self, url: str) -> tuple[str, str, str]:
@@ -109,7 +109,9 @@ class GitHubRepoViewer:
109
109
  """
110
110
  headers = self.base_headers.copy()
111
111
  if self.github_token is not None:
112
- headers["Authorization"] = f"Bearer {self.github_token.resolve_value()}"
112
+ token_value = self.github_token.resolve_value()
113
+ if token_value:
114
+ headers["Authorization"] = f"Bearer {token_value}"
113
115
  return headers
114
116
 
115
117
  def to_dict(self) -> Dict[str, Any]:
@@ -5,6 +5,7 @@ from .context_prompt import CONTEXT_PROMPT
5
5
  from .file_editor_prompt import FILE_EDITOR_PROMPT, FILE_EDITOR_SCHEMA
6
6
  from .issue_commenter_prompt import ISSUE_COMMENTER_PROMPT, ISSUE_COMMENTER_SCHEMA
7
7
  from .pr_creator_prompt import PR_CREATOR_PROMPT, PR_CREATOR_SCHEMA
8
+ from .repo_forker_prompt import REPO_FORKER_PROMPT, REPO_FORKER_SCHEMA
8
9
  from .repo_viewer_prompt import REPO_VIEWER_PROMPT, REPO_VIEWER_SCHEMA
9
10
  from .system_prompt import SYSTEM_PROMPT
10
11
 
@@ -16,6 +17,8 @@ __all__ = [
16
17
  "ISSUE_COMMENTER_SCHEMA",
17
18
  "PR_CREATOR_PROMPT",
18
19
  "PR_CREATOR_SCHEMA",
20
+ "REPO_FORKER_PROMPT",
21
+ "REPO_FORKER_SCHEMA",
19
22
  "REPO_VIEWER_PROMPT",
20
23
  "REPO_VIEWER_SCHEMA",
21
24
  "SYSTEM_PROMPT",
@@ -0,0 +1,61 @@
1
+ # SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ REPO_FORKER_PROMPT = """Haystack-Agent uses this tool to fork GitHub repositories in order to contribute to issues.
5
+ Haystack-Agent initiates a fork so it can freely make changes for contributions.
6
+ A fork is required to open a pull request to the upstream repository.
7
+ Haystack-Agent works by forking the repository associated with a given issue.
8
+
9
+ <usage>
10
+ Pass a `url` string for the GitHub issue you want to work on in a fork.
11
+ It is REQUIRED to pass `url` to use this tool.
12
+ The structure must be "https://github.com/<repo-owner>/<repo-name>/issues/<issue-number>".
13
+
14
+ Examples:
15
+
16
+ - {"url": "https://github.com/deepset-ai/haystack/issues/9343"}
17
+ - will fork the "deepset-ai/haystack" repository to work on issue 9343
18
+ - {"url": "https://github.com/deepset-ai/haystack-core-integrations/issues/1685"}
19
+ - will fork the "deepset-ai/haystack-core-integrations" repository to work on issue 1685
20
+ </usage>
21
+
22
+ Haystack-Agent uses the `repo_forker` tool to create a copy (fork) of the target repository into its own account.
23
+ Haystack-Agent ensures the issue URL is valid and points to a real GitHub issue.
24
+ It parses the URL to identify the correct repository.
25
+
26
+ <thinking>
27
+ - Does this issue belong to the repository I need to work on?
28
+ - Can I extract the owner and repository name from the URL?
29
+ - Why am I forking this repository? (e.g., to implement a fix, to add a feature)
30
+ - Is there anything special about the branch or base state I should be aware of?
31
+ </thinking>
32
+
33
+ Haystack-Agent reflects on the results after forking:
34
+ <thinking>
35
+ - Did the fork succeed? Is the fork visible in my account?
36
+ - Can I access, clone, and push to my fork?
37
+ - Are there any permissions or fork-specific settings to configure before proceeding?
38
+ - Which branch will I be working on in the fork?
39
+ </thinking>
40
+
41
+ IMPORTANT
42
+ Haystack-Agent ONLY forks the repository mentioned in the given issue URL.
43
+ Haystack-Agent does NOT attempt to fork organizations, user profiles, or non-issue URLs.
44
+ Haystack-Agent knows that forking is a prerequisite to contributing changes and creating pull requests.
45
+
46
+ Haystack-Agent takes notes after the fork:
47
+ <scratchpad>
48
+ - Record the URL of the forked repository
49
+ - Note the original issue being worked on
50
+ - Document any post-fork steps (e.g., git cloning, installing dependencies)
51
+ - Make note of any errors or special setup requirements
52
+ </scratchpad>
53
+ """
54
+
55
+ REPO_FORKER_SCHEMA = {
56
+ "properties": {
57
+ "url": {"type": "string", "description": "URL of the GitHub issue to work on in the fork."},
58
+ },
59
+ "required": ["url"],
60
+ "type": "object",
61
+ }
@@ -5,6 +5,7 @@ from .file_editor_tool import GitHubFileEditorTool
5
5
  from .issue_commenter_tool import GitHubIssueCommenterTool
6
6
  from .issue_viewer_tool import GitHubIssueViewerTool
7
7
  from .pr_creator_tool import GitHubPRCreatorTool
8
+ from .repo_forker_tool import GitHubRepoForkerTool
8
9
  from .repo_viewer_tool import GitHubRepoViewerTool
9
10
 
10
11
  __all__ = [
@@ -12,5 +13,6 @@ __all__ = [
12
13
  "GitHubIssueCommenterTool",
13
14
  "GitHubIssueViewerTool",
14
15
  "GitHubPRCreatorTool",
16
+ "GitHubRepoForkerTool",
15
17
  "GitHubRepoViewerTool",
16
18
  ]
@@ -0,0 +1,116 @@
1
+ # SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ from typing import Any, Callable, Dict, Optional, Union
5
+
6
+ from haystack.core.serialization import generate_qualified_class_name
7
+ from haystack.tools import ComponentTool
8
+ from haystack.utils import Secret, deserialize_secrets_inplace
9
+
10
+ from haystack_integrations.components.connectors.github.repo_forker import GitHubRepoForker
11
+ from haystack_integrations.prompts.github.repo_forker_prompt import REPO_FORKER_PROMPT, REPO_FORKER_SCHEMA
12
+ from haystack_integrations.tools.github.utils import deserialize_handlers, serialize_handlers
13
+
14
+
15
+ class GitHubRepoForkerTool(ComponentTool):
16
+ """
17
+ A tool for forking Github repository.
18
+ """
19
+
20
+ def __init__(
21
+ self,
22
+ *,
23
+ name: Optional[str] = "repo_forker",
24
+ description: Optional[str] = REPO_FORKER_PROMPT,
25
+ parameters: Optional[Dict[str, Any]] = REPO_FORKER_SCHEMA,
26
+ github_token: Secret = Secret.from_env_var("GITHUB_TOKEN"),
27
+ raise_on_failure: bool = True,
28
+ outputs_to_string: Optional[Dict[str, Union[str, Callable[[Any], str]]]] = None,
29
+ inputs_from_state: Optional[Dict[str, str]] = None,
30
+ outputs_to_state: Optional[Dict[str, Dict[str, Union[str, Callable]]]] = None,
31
+ ):
32
+ """
33
+ Initialize the GitHub Repo Forker tool.
34
+
35
+ :param name: Optional name for the tool.
36
+ :param description: Optional description.
37
+ :param parameters: Optional JSON schema defining the parameters expected by the Tool.
38
+ :param github_token: GitHub personal access token for API authentication
39
+ :param raise_on_failure: If True, raises exceptions on API errors
40
+ :param outputs_to_string:
41
+ Optional dictionary defining how a tool outputs should be converted into a string.
42
+ If the source is provided only the specified output key is sent to the handler.
43
+ If the source is omitted the whole tool result is sent to the handler.
44
+ Example: {
45
+ "source": "docs", "handler": format_documents
46
+ }
47
+ :param inputs_from_state:
48
+ Optional dictionary mapping state keys to tool parameter names.
49
+ Example: {"repository": "repo"} maps state's "repository" to tool's "repo" parameter.
50
+ :param outputs_to_state:
51
+ Optional dictionary defining how tool outputs map to keys within state as well as optional handlers.
52
+ If the source is provided only the specified output key is sent to the handler.
53
+ Example: {
54
+ "documents": {"source": "docs", "handler": custom_handler}
55
+ }
56
+ If the source is omitted the whole tool result is sent to the handler.
57
+ Example: {
58
+ "documents": {"handler": custom_handler}
59
+ }
60
+ """
61
+ self.github_token = github_token
62
+ self.raise_on_failure = raise_on_failure
63
+ self.outputs_to_string = outputs_to_string
64
+ self.inputs_from_state = inputs_from_state
65
+ self.outputs_to_state = outputs_to_state
66
+
67
+ repo_forker = GitHubRepoForker(
68
+ github_token=github_token,
69
+ raise_on_failure=raise_on_failure,
70
+ )
71
+
72
+ super().__init__(
73
+ component=repo_forker,
74
+ name=name,
75
+ description=description,
76
+ parameters=parameters,
77
+ outputs_to_string=self.outputs_to_string,
78
+ inputs_from_state=self.inputs_from_state,
79
+ outputs_to_state=self.outputs_to_state,
80
+ )
81
+
82
+ def to_dict(self) -> Dict[str, Any]:
83
+ """
84
+ Serializes the tool to a dictionary.
85
+
86
+ Returns:
87
+ Dictionary with serialized data.
88
+ """
89
+ serialized = {
90
+ "name": self.name,
91
+ "description": self.description,
92
+ "parameters": self.parameters,
93
+ "github_token": self.github_token.to_dict() if self.github_token else None,
94
+ "raise_on_failure": self.raise_on_failure,
95
+ "outputs_to_string": self.outputs_to_string,
96
+ "inputs_from_state": self.inputs_from_state,
97
+ "outputs_to_state": self.outputs_to_state,
98
+ }
99
+
100
+ serialize_handlers(serialized, self.outputs_to_state, self.outputs_to_string)
101
+ return {"type": generate_qualified_class_name(type(self)), "data": serialized}
102
+
103
+ @classmethod
104
+ def from_dict(cls, data: Dict[str, Any]) -> "GitHubRepoForkerTool":
105
+ """
106
+ Deserializes the tool from a dictionary.
107
+
108
+ :param data:
109
+ Dictionary to deserialize from.
110
+ :returns:
111
+ Deserialized tool.
112
+ """
113
+ inner_data = data["data"]
114
+ deserialize_secrets_inplace(inner_data, keys=["github_token"])
115
+ deserialize_handlers(inner_data)
116
+ return cls(**inner_data)
@@ -269,3 +269,14 @@ class TestGitHubFileEditor:
269
269
  repo="owner/repo",
270
270
  branch="main",
271
271
  )
272
+
273
+ def test_get_request_headers_with_empty_token(self, monkeypatch):
274
+ monkeypatch.setenv("GITHUB_TOKEN", "")
275
+
276
+ editor = GitHubFileEditor()
277
+
278
+ headers = editor._get_request_headers()
279
+
280
+ assert "Authorization" not in headers
281
+ assert headers["Accept"] == "application/vnd.github.v3+json"
282
+ assert headers["User-Agent"] == "Haystack/GitHubFileEditor"
@@ -141,13 +141,15 @@ class TestGitHubFileEditorTool:
141
141
 
142
142
  pipeline_dict = pipeline.to_dict()
143
143
 
144
- # Remove http_client_kwargs from both dictionaries if it exists
145
- # We don't want to test the http_client_kwargs because Haystack 2.12.0 doesn't have it
146
- # Only Haystack 2.13.0+ has it
147
- if "components" in pipeline_dict:
148
- agent_params = pipeline_dict["components"]["agent"]["init_parameters"]["chat_generator"]["init_parameters"]
149
- if "http_client_kwargs" in agent_params:
150
- del agent_params["http_client_kwargs"]
144
+ # Remove parameters introduced after haystack-ai==2.12.0 (the minimum supported version)
145
+ # to maintain compatibility
146
+ try:
147
+ pipeline_dict["components"]["agent"]["init_parameters"]["chat_generator"]["init_parameters"].pop(
148
+ "http_client_kwargs", None
149
+ )
150
+ pipeline_dict["components"]["agent"]["init_parameters"].pop("tool_invoker_kwargs", None)
151
+ except KeyError:
152
+ pass
151
153
 
152
154
  expected_dict = {
153
155
  "metadata": {},
@@ -114,3 +114,14 @@ class TestGitHubIssueCommenter:
114
114
 
115
115
  with pytest.raises(ValueError):
116
116
  commenter._parse_github_url("https://github.com/invalid/url")
117
+
118
+ def test_get_request_headers_with_empty_token(self, monkeypatch):
119
+ monkeypatch.setenv("GITHUB_TOKEN", "")
120
+
121
+ commenter = GitHubIssueCommenter()
122
+
123
+ headers = commenter._get_request_headers()
124
+
125
+ assert "Authorization" not in headers
126
+ assert headers["Accept"] == "application/vnd.github.v3+json"
127
+ assert headers["User-Agent"] == "Haystack/GitHubIssueCommenter"
@@ -150,3 +150,37 @@ class TestGitHubIssueViewer:
150
150
 
151
151
  with pytest.raises(ValueError):
152
152
  viewer._parse_github_url("https://github.com/invalid/url")
153
+
154
+ def test_get_request_headers_with_valid_token(self, monkeypatch):
155
+ monkeypatch.setenv("GITHUB_TOKEN", "test-token-value")
156
+
157
+ token = Secret.from_env_var("GITHUB_TOKEN")
158
+ viewer = GitHubIssueViewer(github_token=token)
159
+
160
+ headers = viewer._get_request_headers()
161
+
162
+ assert "Authorization" in headers
163
+ assert headers["Authorization"] == "Bearer test-token-value"
164
+ assert headers["Accept"] == "application/vnd.github.v3+json"
165
+ assert headers["User-Agent"] == "Haystack/GitHubIssueViewer"
166
+
167
+ def test_get_request_headers_without_token(self):
168
+ viewer = GitHubIssueViewer(github_token=None)
169
+
170
+ headers = viewer._get_request_headers()
171
+
172
+ assert "Authorization" not in headers
173
+ assert headers["Accept"] == "application/vnd.github.v3+json"
174
+ assert headers["User-Agent"] == "Haystack/GitHubIssueViewer"
175
+
176
+ def test_get_request_headers_with_empty_token(self, monkeypatch):
177
+ monkeypatch.setenv("GITHUB_TOKEN", "")
178
+
179
+ token = Secret.from_env_var("GITHUB_TOKEN")
180
+ viewer = GitHubIssueViewer(github_token=token)
181
+
182
+ headers = viewer._get_request_headers()
183
+
184
+ assert "Authorization" not in headers
185
+ assert headers["Accept"] == "application/vnd.github.v3+json"
186
+ assert headers["User-Agent"] == "Haystack/GitHubIssueViewer"
@@ -132,3 +132,14 @@ class TestGitHubPRCreator:
132
132
  branch="feature-branch",
133
133
  base="main",
134
134
  )
135
+
136
+ def test_get_request_headers_with_empty_token(self, monkeypatch):
137
+ monkeypatch.setenv("GITHUB_TOKEN", "")
138
+
139
+ pr_creator = GitHubPRCreator()
140
+
141
+ headers = pr_creator._get_request_headers()
142
+
143
+ assert "Authorization" not in headers
144
+ assert headers["Accept"] == "application/vnd.github.v3+json"
145
+ assert headers["User-Agent"] == "Haystack/GitHubPRCreator"
@@ -14,7 +14,7 @@ class TestGitHubRepoForker:
14
14
  def test_init_default(self, monkeypatch):
15
15
  monkeypatch.setenv("GITHUB_TOKEN", "test-token")
16
16
 
17
- forker = GitHubRepoForker()
17
+ forker = GitHubRepoForker(github_token=Secret.from_env_var("GITHUB_TOKEN"))
18
18
  assert forker.github_token is not None
19
19
  assert forker.github_token.resolve_value() == "test-token"
20
20
  assert forker.raise_on_failure is True
@@ -261,3 +261,14 @@ class TestGitHubRepoForker:
261
261
 
262
262
  with pytest.raises(ValueError):
263
263
  forker._parse_github_url("https://github.com/invalid/url")
264
+
265
+ def test_get_request_headers_with_empty_token(self, monkeypatch):
266
+ monkeypatch.setenv("GITHUB_TOKEN", "")
267
+
268
+ forker = GitHubRepoForker()
269
+
270
+ headers = forker._get_request_headers()
271
+
272
+ assert "Authorization" not in headers
273
+ assert headers["Accept"] == "application/vnd.github.v3+json"
274
+ assert headers["User-Agent"] == "Haystack/GitHubRepoForker"
@@ -0,0 +1,128 @@
1
+ # SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ from haystack.utils import Secret
5
+
6
+ from haystack_integrations.prompts.github.repo_forker_prompt import REPO_FORKER_PROMPT, REPO_FORKER_SCHEMA
7
+ from haystack_integrations.tools.github.repo_forker_tool import GitHubRepoForkerTool
8
+ from haystack_integrations.tools.github.utils import message_handler
9
+
10
+
11
+ class TestGitHubRepoForkerTool:
12
+ def test_init(self, monkeypatch):
13
+ monkeypatch.setenv("GITHUB_TOKEN", "test-token")
14
+
15
+ tool = GitHubRepoForkerTool()
16
+ assert tool.name == "repo_forker"
17
+ assert tool.description == REPO_FORKER_PROMPT
18
+ assert tool.parameters == REPO_FORKER_SCHEMA
19
+ assert tool.github_token == Secret.from_env_var("GITHUB_TOKEN")
20
+ assert tool.raise_on_failure is True
21
+ assert tool.outputs_to_string is None
22
+ assert tool.inputs_from_state is None
23
+ assert tool.outputs_to_state is None
24
+
25
+ def test_from_dict(self, monkeypatch):
26
+ monkeypatch.setenv("GITHUB_TOKEN", "test-token")
27
+ tool_dict = {
28
+ "type": "haystack_integrations.tools.github.repo_forker_tool.GitHubRepoForkerTool",
29
+ "data": {
30
+ "name": "repo_forker",
31
+ "description": REPO_FORKER_PROMPT,
32
+ "parameters": REPO_FORKER_SCHEMA,
33
+ "github_token": {"env_vars": ["GITHUB_TOKEN"], "strict": True, "type": "env_var"},
34
+ "raise_on_failure": True,
35
+ "outputs_to_string": None,
36
+ "inputs_from_state": None,
37
+ "outputs_to_state": None,
38
+ },
39
+ }
40
+ tool = GitHubRepoForkerTool.from_dict(tool_dict)
41
+ assert tool.name == "repo_forker"
42
+ assert tool.description == REPO_FORKER_PROMPT
43
+ assert tool.parameters == REPO_FORKER_SCHEMA
44
+ assert tool.github_token == Secret.from_env_var("GITHUB_TOKEN")
45
+ assert tool.raise_on_failure is True
46
+ assert tool.outputs_to_string is None
47
+ assert tool.inputs_from_state is None
48
+ assert tool.outputs_to_state is None
49
+
50
+ def test_to_dict(self, monkeypatch):
51
+ monkeypatch.setenv("GITHUB_TOKEN", "test-token")
52
+ tool = GitHubRepoForkerTool()
53
+ tool_dict = tool.to_dict()
54
+ assert tool_dict["type"] == "haystack_integrations.tools.github.repo_forker_tool.GitHubRepoForkerTool"
55
+ assert tool_dict["data"]["name"] == "repo_forker"
56
+ assert tool_dict["data"]["description"] == REPO_FORKER_PROMPT
57
+ assert tool_dict["data"]["parameters"] == REPO_FORKER_SCHEMA
58
+ assert tool_dict["data"]["github_token"] == {
59
+ "env_vars": ["GITHUB_TOKEN"],
60
+ "strict": True,
61
+ "type": "env_var",
62
+ }
63
+ assert tool_dict["data"]["raise_on_failure"] is True
64
+ assert tool_dict["data"]["outputs_to_string"] is None
65
+ assert tool_dict["data"]["inputs_from_state"] is None
66
+ assert tool_dict["data"]["outputs_to_state"] is None
67
+
68
+ def test_to_dict_with_extra_params(self, monkeypatch):
69
+ monkeypatch.setenv("GITHUB_TOKEN", "test-token")
70
+ tool = GitHubRepoForkerTool(
71
+ github_token=Secret.from_env_var("GITHUB_TOKEN"),
72
+ raise_on_failure=False,
73
+ outputs_to_string={"source": "docs", "handler": message_handler},
74
+ inputs_from_state={"repository": "repo"},
75
+ outputs_to_state={"documents": {"source": "docs", "handler": message_handler}},
76
+ )
77
+ tool_dict = tool.to_dict()
78
+ assert tool_dict["type"] == "haystack_integrations.tools.github.repo_forker_tool.GitHubRepoForkerTool"
79
+ assert tool_dict["data"]["name"] == "repo_forker"
80
+ assert tool_dict["data"]["description"] == REPO_FORKER_PROMPT
81
+ assert tool_dict["data"]["parameters"] == REPO_FORKER_SCHEMA
82
+ assert tool_dict["data"]["github_token"] == {
83
+ "env_vars": ["GITHUB_TOKEN"],
84
+ "strict": True,
85
+ "type": "env_var",
86
+ }
87
+ assert tool_dict["data"]["raise_on_failure"] is False
88
+ assert (
89
+ tool_dict["data"]["outputs_to_string"]["handler"]
90
+ == "haystack_integrations.tools.github.utils.message_handler"
91
+ )
92
+ assert tool_dict["data"]["inputs_from_state"] == {"repository": "repo"}
93
+ assert tool_dict["data"]["outputs_to_state"]["documents"]["source"] == "docs"
94
+ assert (
95
+ tool_dict["data"]["outputs_to_state"]["documents"]["handler"]
96
+ == "haystack_integrations.tools.github.utils.message_handler"
97
+ )
98
+
99
+ def test_from_dict_with_extra_params(self, monkeypatch):
100
+ monkeypatch.setenv("GITHUB_TOKEN", "test-token")
101
+ tool_dict = {
102
+ "type": "haystack_integrations.tools.github.repo_forker_tool.GitHubRepoForkerTool",
103
+ "data": {
104
+ "name": "repo_forker",
105
+ "description": REPO_FORKER_PROMPT,
106
+ "parameters": REPO_FORKER_SCHEMA,
107
+ "github_token": {"env_vars": ["GITHUB_TOKEN"], "strict": True, "type": "env_var"},
108
+ "raise_on_failure": False,
109
+ "outputs_to_string": {"handler": "haystack_integrations.tools.github.utils.message_handler"},
110
+ "inputs_from_state": {"repository": "repo"},
111
+ "outputs_to_state": {
112
+ "documents": {
113
+ "source": "docs",
114
+ "handler": "haystack_integrations.tools.github.utils.message_handler",
115
+ }
116
+ },
117
+ },
118
+ }
119
+ tool = GitHubRepoForkerTool.from_dict(tool_dict)
120
+ assert tool.name == "repo_forker"
121
+ assert tool.description == REPO_FORKER_PROMPT
122
+ assert tool.parameters == REPO_FORKER_SCHEMA
123
+ assert tool.github_token == Secret.from_env_var("GITHUB_TOKEN")
124
+ assert tool.raise_on_failure is False
125
+ assert tool.outputs_to_string["handler"] == message_handler
126
+ assert tool.inputs_from_state == {"repository": "repo"}
127
+ assert tool.outputs_to_state["documents"]["source"] == "docs"
128
+ assert tool.outputs_to_state["documents"]["handler"] == message_handler
@@ -174,3 +174,15 @@ class TestGitHubRepoViewer:
174
174
 
175
175
  with pytest.raises(ValueError):
176
176
  viewer._parse_repo("invalid_format")
177
+
178
+ def test_get_request_headers_with_empty_token(self, monkeypatch):
179
+ monkeypatch.setenv("GITHUB_TOKEN", "")
180
+
181
+ token = Secret.from_env_var("GITHUB_TOKEN")
182
+ viewer = GitHubRepoViewer(github_token=token)
183
+
184
+ headers = viewer._get_request_headers()
185
+
186
+ assert "Authorization" not in headers
187
+ assert headers["Accept"] == "application/vnd.github.v3+json"
188
+ assert headers["User-Agent"] == "Haystack/GitHubRepoViewer"
@@ -1,20 +0,0 @@
1
- # Changelog
2
-
3
- ## [integrations/github-v1.0.1] - 2025-06-12
4
-
5
- ### 🐛 Bug Fixes
6
-
7
- - Prompts and schemas of GitHubIssueCommenterTool and GitHubIssueViewerTool now properly explain url parameter (#1929)
8
-
9
-
10
- ### 🧹 Chores
11
-
12
- - Align core-integrations Hatch scripts (#1898)
13
-
14
- ## [integrations/github-v1.0.0] - 2025-05-28
15
-
16
- ### 🚀 Features
17
-
18
- - Add GitHub integration with components, tools, and prompts (#1637)
19
-
20
- <!-- generated by git-cliff -->
@@ -1,21 +0,0 @@
1
- # github-haystack
2
-
3
- [![PyPI - Version](https://img.shields.io/pypi/v/github-haystack.svg)](https://pypi.org/project/github-haystack)
4
- [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/github-haystack.svg)](https://pypi.org/project/github-haystack)
5
-
6
- -----
7
-
8
- ## Table of Contents
9
-
10
- - [Installation](#installation)
11
- - [License](#license)
12
-
13
- ## Installation
14
-
15
- ```console
16
- pip install github-haystack
17
- ```
18
-
19
- ## License
20
-
21
- `github-haystack` is distributed under the terms of the [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) license.