github-haystack 1.0.1__tar.gz → 1.2.0__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.0/CHANGELOG.md +32 -0
  2. {github_haystack-1.0.1 → github_haystack-1.2.0}/PKG-INFO +7 -16
  3. github_haystack-1.2.0/README.md +12 -0
  4. {github_haystack-1.0.1 → github_haystack-1.2.0}/pyproject.toml +8 -29
  5. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/components/connectors/github/file_editor.py +1 -1
  6. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/components/connectors/github/repo_forker.py +3 -4
  7. github_haystack-1.2.0/src/haystack_integrations/components/connectors/py.typed +0 -0
  8. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/prompts/github/__init__.py +3 -0
  9. github_haystack-1.2.0/src/haystack_integrations/prompts/github/repo_forker_prompt.py +61 -0
  10. github_haystack-1.2.0/src/haystack_integrations/prompts/py.typed +0 -0
  11. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/tools/github/__init__.py +2 -0
  12. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/tools/github/file_editor_tool.py +0 -3
  13. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/tools/github/issue_commenter_tool.py +0 -3
  14. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/tools/github/issue_viewer_tool.py +0 -3
  15. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/tools/github/pr_creator_tool.py +0 -3
  16. github_haystack-1.2.0/src/haystack_integrations/tools/github/repo_forker_tool.py +116 -0
  17. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/tools/github/repo_viewer_tool.py +0 -3
  18. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/tools/github/utils.py +11 -5
  19. github_haystack-1.2.0/src/haystack_integrations/tools/py.typed +0 -0
  20. {github_haystack-1.0.1 → github_haystack-1.2.0}/tests/test_file_editor_tool.py +9 -7
  21. {github_haystack-1.0.1 → github_haystack-1.2.0}/tests/test_repo_forker.py +1 -1
  22. github_haystack-1.2.0/tests/test_repo_forker_tool.py +128 -0
  23. github_haystack-1.0.1/CHANGELOG.md +0 -9
  24. github_haystack-1.0.1/README.md +0 -21
  25. {github_haystack-1.0.1 → github_haystack-1.2.0}/.gitignore +0 -0
  26. {github_haystack-1.0.1 → github_haystack-1.2.0}/LICENSE.txt +0 -0
  27. {github_haystack-1.0.1 → github_haystack-1.2.0}/pydoc/config.yml +0 -0
  28. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/components/connectors/github/__init__.py +0 -0
  29. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/components/connectors/github/issue_commenter.py +0 -0
  30. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/components/connectors/github/issue_viewer.py +0 -0
  31. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/components/connectors/github/pr_creator.py +0 -0
  32. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/components/connectors/github/repo_viewer.py +0 -0
  33. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/prompts/github/context_prompt.py +0 -0
  34. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/prompts/github/file_editor_prompt.py +0 -0
  35. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/prompts/github/issue_commenter_prompt.py +0 -0
  36. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/prompts/github/issue_viewer_prompt.py +0 -0
  37. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/prompts/github/pr_creator_prompt.py +0 -0
  38. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/prompts/github/repo_viewer_prompt.py +0 -0
  39. {github_haystack-1.0.1 → github_haystack-1.2.0}/src/haystack_integrations/prompts/github/system_prompt.py +0 -0
  40. {github_haystack-1.0.1 → github_haystack-1.2.0}/tests/__init__.py +0 -0
  41. {github_haystack-1.0.1 → github_haystack-1.2.0}/tests/test_file_editor.py +0 -0
  42. {github_haystack-1.0.1 → github_haystack-1.2.0}/tests/test_issue_commenter.py +0 -0
  43. {github_haystack-1.0.1 → github_haystack-1.2.0}/tests/test_issue_commenter_tool.py +0 -0
  44. {github_haystack-1.0.1 → github_haystack-1.2.0}/tests/test_issue_viewer.py +0 -0
  45. {github_haystack-1.0.1 → github_haystack-1.2.0}/tests/test_issue_viewer_tool.py +0 -0
  46. {github_haystack-1.0.1 → github_haystack-1.2.0}/tests/test_pr_creator.py +0 -0
  47. {github_haystack-1.0.1 → github_haystack-1.2.0}/tests/test_pr_creator_tool.py +0 -0
  48. {github_haystack-1.0.1 → github_haystack-1.2.0}/tests/test_repo_viewer.py +0 -0
  49. {github_haystack-1.0.1 → github_haystack-1.2.0}/tests/test_repo_viewer_tool.py +0 -0
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ ## [integrations/github-v1.1.0] - 2025-07-01
4
+
5
+ ### 🐛 Bug Fixes
6
+
7
+ - Fix Github types + add py.typed (#2024)
8
+
9
+ ### 🧹 Chores
10
+
11
+ - Fix linting for ruff 0.12.0 (#1969)
12
+ - Remove black (#1985)
13
+
14
+
15
+ ## [integrations/github-v1.0.1] - 2025-06-12
16
+
17
+ ### 🐛 Bug Fixes
18
+
19
+ - Prompts and schemas of GitHubIssueCommenterTool and GitHubIssueViewerTool now properly explain url parameter (#1929)
20
+
21
+
22
+ ### 🧹 Chores
23
+
24
+ - Align core-integrations Hatch scripts (#1898)
25
+
26
+ ## [integrations/github-v1.0.0] - 2025-05-28
27
+
28
+ ### 🚀 Features
29
+
30
+ - Add GitHub integration with components, tools, and prompts (#1637)
31
+
32
+ <!-- generated by git-cliff -->
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github-haystack
3
- Version: 1.0.1
3
+ Version: 1.2.0
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).
@@ -66,23 +66,15 @@ integration = 'pytest -m "integration" {args:tests}'
66
66
  all = 'pytest {args:tests}'
67
67
  cov-retry = 'all --cov=haystack_integrations --reruns 3 --reruns-delay 30 -x'
68
68
 
69
- types = "mypy --install-types --non-interactive --explicit-package-bases {args:src/ tests}"
69
+ types = """mypy -p haystack_integrations.components.connectors.github \
70
+ -p haystack_integrations.prompts.github \
71
+ -p haystack_integrations.tools.github {args}"""
70
72
 
71
- # TODO: remove lint environment once this integration is properly typed
72
- # test environment should be used instead
73
- # https://github.com/deepset-ai/haystack-core-integrations/issues/1771
74
- [tool.hatch.envs.lint]
75
- installer = "uv"
76
- detached = true
77
- dependencies = ["pip", "black>=23.1.0", "mypy>=1.0.0", "ruff>=0.0.243"]
78
-
79
- [tool.hatch.envs.lint.scripts]
80
- typing = "mypy --install-types --non-interactive --explicit-package-bases {args:src/ tests}"
81
-
82
- [tool.black]
83
- target-version = ["py38"]
84
- line-length = 120
85
- skip-string-normalization = true
73
+ [tool.mypy]
74
+ install_types = true
75
+ non_interactive = true
76
+ check_untyped_defs = true
77
+ disallow_incomplete_defs = true
86
78
 
87
79
  [tool.ruff]
88
80
  target-version = "py38"
@@ -160,22 +152,9 @@ omit = ["*/tests/*", "*/__init__.py"]
160
152
  show_missing = true
161
153
  exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"]
162
154
 
163
-
164
- [[tool.mypy.overrides]]
165
- module = [
166
- "github.*",
167
- "haystack.*",
168
- "haystack_integrations.*",
169
- "openai.*",
170
- "pytest.*",
171
- "numpy.*",
172
- ]
173
- ignore_missing_imports = true
174
-
175
155
  [tool.pytest.ini_options]
176
156
  addopts = "--strict-markers"
177
157
  markers = [
178
158
  "integration: integration tests",
179
- "unit: unit tests",
180
159
  ]
181
160
  log_cli = true
@@ -225,7 +225,7 @@ class GitHubFileEditor:
225
225
  def _delete_file(self, owner: str, repo: str, payload: Dict[str, str], branch: str) -> str:
226
226
  """Handle file deletion."""
227
227
  try:
228
- content, sha = self._get_file_content(owner, repo, payload["path"], branch)
228
+ _, sha = self._get_file_content(owner, repo, payload["path"], branch)
229
229
  url = f"https://api.github.com/repos/{owner}/{repo}/contents/{payload['path']}"
230
230
 
231
231
  data = {"message": payload["message"], "sha": sha, "branch": branch}
@@ -2,6 +2,7 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
  import re
5
+ import time
5
6
  from typing import Any, Dict, Optional
6
7
 
7
8
  import requests
@@ -59,9 +60,9 @@ class GitHubRepoForker:
59
60
  :param auto_sync: If True, syncs fork with original repository if it already exists
60
61
  :param create_branch: If True, creates a fix branch based on the issue number
61
62
  """
62
- error_message = "github_token must be a Secret"
63
63
  if not isinstance(github_token, Secret):
64
- raise TypeError(error_message)
64
+ msg = "github_token must be a Secret"
65
+ raise TypeError(msg)
65
66
 
66
67
  self.github_token = github_token
67
68
  self.raise_on_failure = raise_on_failure
@@ -274,8 +275,6 @@ class GitHubRepoForker:
274
275
 
275
276
  # Wait for fork completion if requested
276
277
  if self.wait_for_completion:
277
- import time
278
-
279
278
  start_time = time.time()
280
279
 
281
280
  while time.time() - start_time < self.max_wait_seconds:
@@ -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
  ]
@@ -62,9 +62,6 @@ class GitHubFileEditorTool(ComponentTool):
62
62
  "documents": {"handler": custom_handler}
63
63
  }
64
64
  """
65
- self.name = name
66
- self.description = description
67
- self.parameters = parameters
68
65
  self.github_token = github_token
69
66
  self.repo = repo
70
67
  self.branch = branch
@@ -60,9 +60,6 @@ class GitHubIssueCommenterTool(ComponentTool):
60
60
  "documents": {"handler": custom_handler}
61
61
  }
62
62
  """
63
- self.name = name
64
- self.description = description
65
- self.parameters = parameters
66
63
  self.github_token = github_token
67
64
  self.raise_on_failure = raise_on_failure
68
65
  self.retry_attempts = retry_attempts
@@ -60,9 +60,6 @@ class GitHubIssueViewerTool(ComponentTool):
60
60
  "documents": {"handler": custom_handler}
61
61
  }
62
62
  """
63
- self.name = name
64
- self.description = description
65
- self.parameters = parameters
66
63
  self.github_token = github_token
67
64
  self.raise_on_failure = raise_on_failure
68
65
  self.retry_attempts = retry_attempts
@@ -58,9 +58,6 @@ class GitHubPRCreatorTool(ComponentTool):
58
58
  "documents": {"handler": custom_handler}
59
59
  }
60
60
  """
61
- self.name = name
62
- self.description = description
63
- self.parameters = parameters
64
61
  self.github_token = github_token
65
62
  self.raise_on_failure = raise_on_failure
66
63
  self.outputs_to_string = outputs_to_string
@@ -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)
@@ -67,9 +67,6 @@ class GitHubRepoViewerTool(ComponentTool):
67
67
  "documents": {"handler": custom_handler}
68
68
  }
69
69
  """
70
- self.name = name
71
- self.description = description
72
- self.parameters = parameters
73
70
  self.github_token = github_token
74
71
  self.repo = repo
75
72
  self.branch = branch
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
- from typing import Any, Callable, Dict, List, Union
5
+ from typing import Any, Callable, Dict, List, Optional, Union
6
6
 
7
7
  from haystack import Document
8
8
  from haystack.utils.callable_serialization import deserialize_callable, serialize_callable
@@ -20,10 +20,10 @@ def message_handler(documents: List[Document], max_length: int = 150_000) -> str
20
20
  result_str = ""
21
21
  for document in documents:
22
22
  if document.meta["type"] in ["file", "dir", "error"]:
23
- result_str += document.content + "\n"
23
+ result_str += document.content or "" + "\n"
24
24
  else:
25
25
  result_str += f"File Content for {document.meta['path']}\n\n"
26
- result_str += document.content
26
+ result_str += document.content or ""
27
27
 
28
28
  if len(result_str) > max_length:
29
29
  result_str = result_str[:max_length] + "...(large file can't be fully displayed)"
@@ -33,8 +33,8 @@ def message_handler(documents: List[Document], max_length: int = 150_000) -> str
33
33
 
34
34
  def serialize_handlers(
35
35
  serialized: Dict[str, Any],
36
- outputs_to_state: Dict[str, Dict[str, Union[str, Callable]]],
37
- outputs_to_string: Dict[str, Union[str, Callable[[Any], str]]],
36
+ outputs_to_state: Optional[Dict[str, Dict[str, Union[str, Callable]]]],
37
+ outputs_to_string: Optional[Dict[str, Union[str, Callable[[Any], str]]]],
38
38
  ) -> None:
39
39
  """
40
40
  Serializes callable handlers in outputs_to_state and outputs_to_string.
@@ -48,12 +48,18 @@ def serialize_handlers(
48
48
  for key, config in outputs_to_state.items():
49
49
  serialized_config = config.copy()
50
50
  if "handler" in config:
51
+ if not callable(config["handler"]):
52
+ msg = f"Handler for outputs_to_state[{key}] is not a callable"
53
+ raise ValueError(msg)
51
54
  serialized_config["handler"] = serialize_callable(config["handler"])
52
55
  serialized_outputs[key] = serialized_config
53
56
  serialized["outputs_to_state"] = serialized_outputs
54
57
 
55
58
  if outputs_to_string is not None and "handler" in outputs_to_string:
56
59
  serialized_string = outputs_to_string.copy()
60
+ if not callable(outputs_to_string["handler"]):
61
+ msg = "Handler for outputs_to_string is not a callable"
62
+ raise ValueError(msg)
57
63
  serialized_string["handler"] = serialize_callable(outputs_to_string["handler"])
58
64
  serialized["outputs_to_string"] = serialized_string
59
65
 
@@ -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": {},
@@ -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
@@ -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
@@ -1,9 +0,0 @@
1
- # Changelog
2
-
3
- ## [integrations/github-v1.0.0] - 2025-05-28
4
-
5
- ### 🚀 Features
6
-
7
- - Add GitHub integration with components, tools, and prompts (#1637)
8
-
9
- <!-- 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.