github-agent 0.14.0__tar.gz → 0.19.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.
- {github_agent-0.14.0 → github_agent-0.19.0}/PKG-INFO +38 -12
- {github_agent-0.14.0 → github_agent-0.19.0}/README.md +36 -10
- {github_agent-0.14.0 → github_agent-0.19.0}/github_agent/agent_server.py +1 -1
- github_agent-0.19.0/github_agent/api/__init__.py +1 -0
- github_agent-0.19.0/github_agent/api/api_client_base.py +147 -0
- github_agent-0.19.0/github_agent/api/api_client_branches.py +114 -0
- github_agent-0.19.0/github_agent/api/api_client_commits.py +46 -0
- github_agent-0.19.0/github_agent/api/api_client_contents.py +112 -0
- github_agent-0.19.0/github_agent/api/api_client_issues.py +84 -0
- github_agent-0.19.0/github_agent/api/api_client_orgs.py +40 -0
- github_agent-0.19.0/github_agent/api/api_client_pulls.py +88 -0
- github_agent-0.19.0/github_agent/api/api_client_releases.py +115 -0
- github_agent-0.19.0/github_agent/api/api_client_repos.py +200 -0
- github_agent-0.19.0/github_agent/api/api_client_search.py +62 -0
- github_agent-0.19.0/github_agent/api/api_client_workflows.py +134 -0
- github_agent-0.19.0/github_agent/api_client.py +41 -0
- {github_agent-0.14.0 → github_agent-0.19.0}/github_agent/auth.py +29 -13
- {github_agent-0.14.0 → github_agent-0.19.0}/github_agent/github_input_models.py +123 -25
- {github_agent-0.14.0 → github_agent-0.19.0}/github_agent/github_response_models.py +57 -0
- github_agent-0.19.0/github_agent/mcp/__init__.py +31 -0
- github_agent-0.19.0/github_agent/mcp/mcp_action.py +165 -0
- github_agent-0.19.0/github_agent/mcp/mcp_branch.py +105 -0
- github_agent-0.19.0/github_agent/mcp/mcp_collaborator.py +91 -0
- github_agent-0.19.0/github_agent/mcp/mcp_commit.py +70 -0
- github_agent-0.19.0/github_agent/mcp/mcp_content.py +142 -0
- github_agent-0.19.0/github_agent/mcp/mcp_issue.py +106 -0
- github_agent-0.19.0/github_agent/mcp/mcp_org.py +75 -0
- github_agent-0.19.0/github_agent/mcp/mcp_pull.py +110 -0
- github_agent-0.19.0/github_agent/mcp/mcp_release.py +134 -0
- github_agent-0.19.0/github_agent/mcp/mcp_repo.py +113 -0
- github_agent-0.19.0/github_agent/mcp/mcp_search.py +68 -0
- {github_agent-0.14.0 → github_agent-0.19.0}/github_agent/mcp_server.py +562 -1
- {github_agent-0.14.0 → github_agent-0.19.0}/github_agent.egg-info/PKG-INFO +38 -12
- github_agent-0.19.0/github_agent.egg-info/SOURCES.txt +55 -0
- {github_agent-0.14.0 → github_agent-0.19.0}/github_agent.egg-info/requires.txt +1 -1
- {github_agent-0.14.0 → github_agent-0.19.0}/pyproject.toml +2 -2
- github_agent-0.19.0/tests/conftest.py +11 -0
- github_agent-0.19.0/tests/test_api_client_endpoints.py +162 -0
- github_agent-0.19.0/tests/test_api_validation.py +80 -0
- github_agent-0.19.0/tests/test_auth_client_edge_cases.py +198 -0
- github_agent-0.19.0/tests/test_github_agent_api_brute_force_coverage.py +193 -0
- github_agent-0.19.0/tests/test_init_dynamics.py +51 -0
- github_agent-0.19.0/tests/test_mcp_coverage.py +1036 -0
- github_agent-0.19.0/tests/test_model_fields.py +109 -0
- github_agent-0.19.0/tests/test_startup.py +38 -0
- github_agent-0.14.0/github_agent/api_client.py +0 -546
- github_agent-0.14.0/github_agent.egg-info/SOURCES.txt +0 -24
- github_agent-0.14.0/tests/test_github_agent_api_brute_force_coverage.py +0 -107
- github_agent-0.14.0/tests/test_startup.py +0 -12
- {github_agent-0.14.0 → github_agent-0.19.0}/LICENSE +0 -0
- {github_agent-0.14.0 → github_agent-0.19.0}/github_agent/__init__.py +0 -0
- {github_agent-0.14.0 → github_agent-0.19.0}/github_agent/__main__.py +0 -0
- {github_agent-0.14.0 → github_agent-0.19.0}/github_agent/mcp_config.json +0 -0
- {github_agent-0.14.0 → github_agent-0.19.0}/github_agent.egg-info/dependency_links.txt +0 -0
- {github_agent-0.14.0 → github_agent-0.19.0}/github_agent.egg-info/entry_points.txt +0 -0
- {github_agent-0.14.0 → github_agent-0.19.0}/github_agent.egg-info/top_level.txt +0 -0
- {github_agent-0.14.0 → github_agent-0.19.0}/scripts/validate_a2a_agent.py +0 -0
- {github_agent-0.14.0 → github_agent-0.19.0}/scripts/verify_api_integration.py +0 -0
- {github_agent-0.14.0 → github_agent-0.19.0}/setup.cfg +0 -0
- {github_agent-0.14.0 → github_agent-0.19.0}/tests/test_concept_parity.py +0 -0
- {github_agent-0.14.0 → github_agent-0.19.0}/tests/test_github_agent_brute_force_coverage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: github-agent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.0
|
|
4
4
|
Summary: GitHub Agent for MCP
|
|
5
5
|
Author-email: Audel Rouhi <knucklessg1@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Requires-Python: <3.14,>=3.11
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: agent-utilities[agent,logfire]>=0.
|
|
15
|
+
Requires-Dist: agent-utilities[agent,logfire]>=0.26.0
|
|
16
16
|
Provides-Extra: test
|
|
17
17
|
Requires-Dist: pytest-xdist>=3.6.0; extra == "test"
|
|
18
18
|
Requires-Dist: pytest; extra == "test"
|
|
@@ -41,7 +41,7 @@ Dynamic: license-file
|
|
|
41
41
|

|
|
42
42
|

|
|
43
43
|
|
|
44
|
-
*Version: 0.
|
|
44
|
+
*Version: 0.19.0*
|
|
45
45
|
|
|
46
46
|
---
|
|
47
47
|
|
|
@@ -64,7 +64,7 @@ Dynamic: license-file
|
|
|
64
64
|
|
|
65
65
|
This agent wraps the GitHub Agent for MCP API. You can interact with it programmatically or via its integrated execution entrypoints.
|
|
66
66
|
|
|
67
|
-
Detailed instructions on how to use the underlying API wrappers, extended schema bindings, and developer SDK references are maintained in [docs/index.md](
|
|
67
|
+
Detailed instructions on how to use the underlying API wrappers, extended schema bindings, and developer SDK references are maintained in [docs/index.md](docs/index.md).
|
|
68
68
|
|
|
69
69
|
---
|
|
70
70
|
|
|
@@ -75,14 +75,40 @@ This server utilizes dynamic Action-Routed tools to optimize token overhead and
|
|
|
75
75
|
### Available MCP Tools
|
|
76
76
|
| Tool Module | Toggle Env Var | Enabled by Default | Description & Nested Methods |
|
|
77
77
|
|-------------|----------------|--------------------|------------------------------|
|
|
78
|
-
| **Repo** | `
|
|
79
|
-
| **Issue** | `
|
|
80
|
-
| **Pull** | `
|
|
81
|
-
| **Content** | `
|
|
82
|
-
| **Branch** | `
|
|
83
|
-
| **Commit** | `
|
|
78
|
+
| **Repo** | `REPO_TOOL` | `True` | Manage GitHub repositories. Action-routed methods: `create`, `delete`, `get`, `list`, `update`. |
|
|
79
|
+
| **Issue** | `ISSUE_TOOL` | `True` | Manage GitHub issues. Action-routed methods: `create`, `get`, `list`, `update`. |
|
|
80
|
+
| **Pull** | `PULL_TOOL` | `True` | Manage GitHub pull requests. Action-routed methods: `create`, `get`, `list`, `update`. |
|
|
81
|
+
| **Content** | `CONTENT_TOOL` | `True` | Manage GitHub contents. Action-routed methods: `create`, `delete`, `get`, `update`. |
|
|
82
|
+
| **Branch** | `BRANCH_TOOL` | `True` | Manage GitHub branches. Action-routed methods: `create`, `delete`, `get`, `list`. |
|
|
83
|
+
| **Commit** | `COMMIT_TOOL` | `True` | Manage GitHub commits. Action-routed methods: `get`, `list`. |
|
|
84
|
+
| **Search** | `SEARCH_TOOL` | `True` | Search GitHub repositories, issues, or code. Action-routed methods: `code`, `issues`, `repositories`. |
|
|
85
|
+
| **Org** | `ORG_TOOL` | `True` | Manage GitHub organizations. Action-routed methods: `members`, `repos`, `teams`. |
|
|
86
|
+
| **Collaborator** | `COLLABORATOR_TOOL` | `True` | Manage repository collaborators. Action-routed methods: `add`, `list`, `remove`. |
|
|
87
|
+
| **Action** | `ACTION_TOOL` | `True` | Manage GitHub actions workflows and runs. Action-routed methods: `cancel`, `delete_run`, `get_run`, `list_runs`, `list_workflows`, `rerun`, `trigger_dispatch`. |
|
|
88
|
+
| **Release** | `RELEASE_TOOL` | `True` | Manage repository releases. Action-routed methods: `create`, `delete`, `get`, `list`, `update`. |
|
|
89
|
+
|
|
90
|
+
Detailed tool schemas, parameter shapes, and validation constraints are preserved in [docs/mcp.md](docs/mcp.md).
|
|
91
|
+
|
|
92
|
+
### Dynamic Tool Selection & Visibility
|
|
93
|
+
|
|
94
|
+
This MCP server supports dynamic toolset selection and visibility filtering at runtime. This allows you to restrict the set of exposed tools in order to prevent blowing up the LLM's context window.
|
|
95
|
+
|
|
96
|
+
You can configure tool filtering via multiple input channels:
|
|
97
|
+
|
|
98
|
+
- **CLI Arguments:** Pass `--tools` or `--toolsets` (or their disabled counterparts `--disabled-tools` and `--disabled-toolsets`) during startup.
|
|
99
|
+
- **Environment Variables:** Define standard environment variables:
|
|
100
|
+
- `MCP_ENABLED_TOOLS` / `MCP_DISABLED_TOOLS`
|
|
101
|
+
- `MCP_ENABLED_TAGS` / `MCP_DISABLED_TAGS`
|
|
102
|
+
- **HTTP SSE Request Headers:** Pass custom headers during transport initialization:
|
|
103
|
+
- `x-mcp-enabled-tools` / `x-mcp-disabled-tools`
|
|
104
|
+
- `x-mcp-enabled-tags` / `x-mcp-disabled-tags`
|
|
105
|
+
- **HTTP SSE Request Query Parameters:** Append query parameters directly to your transport connection URL:
|
|
106
|
+
- `?tools=tool1,tool2`
|
|
107
|
+
- `?tags=tag1`
|
|
108
|
+
|
|
109
|
+
When query strings or parameters are supplied, an LLM-free **Knowledge Graph resolution layer** (using `DynamicToolOrchestrator`) matches query intents against known tool tags, names, or descriptions, with safe fallback and automated 24-hour background cache refreshing.
|
|
84
110
|
|
|
85
|
-
|
|
111
|
+
---
|
|
86
112
|
|
|
87
113
|
### MCP Configuration Examples
|
|
88
114
|
|
|
@@ -256,7 +282,7 @@ services:
|
|
|
256
282
|
|
|
257
283
|
```
|
|
258
284
|
|
|
259
|
-
Detailed graph node architecture explanations, custom skill configurations, and agentic trace guides are available in [docs/agent.md](
|
|
285
|
+
Detailed graph node architecture explanations, custom skill configurations, and agentic trace guides are available in [docs/agent.md](docs/agent.md).
|
|
260
286
|
|
|
261
287
|
---
|
|
262
288
|
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|

|
|
21
21
|

|
|
22
22
|
|
|
23
|
-
*Version: 0.
|
|
23
|
+
*Version: 0.19.0*
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
|
|
44
44
|
This agent wraps the GitHub Agent for MCP API. You can interact with it programmatically or via its integrated execution entrypoints.
|
|
45
45
|
|
|
46
|
-
Detailed instructions on how to use the underlying API wrappers, extended schema bindings, and developer SDK references are maintained in [docs/index.md](
|
|
46
|
+
Detailed instructions on how to use the underlying API wrappers, extended schema bindings, and developer SDK references are maintained in [docs/index.md](docs/index.md).
|
|
47
47
|
|
|
48
48
|
---
|
|
49
49
|
|
|
@@ -54,14 +54,40 @@ This server utilizes dynamic Action-Routed tools to optimize token overhead and
|
|
|
54
54
|
### Available MCP Tools
|
|
55
55
|
| Tool Module | Toggle Env Var | Enabled by Default | Description & Nested Methods |
|
|
56
56
|
|-------------|----------------|--------------------|------------------------------|
|
|
57
|
-
| **Repo** | `
|
|
58
|
-
| **Issue** | `
|
|
59
|
-
| **Pull** | `
|
|
60
|
-
| **Content** | `
|
|
61
|
-
| **Branch** | `
|
|
62
|
-
| **Commit** | `
|
|
57
|
+
| **Repo** | `REPO_TOOL` | `True` | Manage GitHub repositories. Action-routed methods: `create`, `delete`, `get`, `list`, `update`. |
|
|
58
|
+
| **Issue** | `ISSUE_TOOL` | `True` | Manage GitHub issues. Action-routed methods: `create`, `get`, `list`, `update`. |
|
|
59
|
+
| **Pull** | `PULL_TOOL` | `True` | Manage GitHub pull requests. Action-routed methods: `create`, `get`, `list`, `update`. |
|
|
60
|
+
| **Content** | `CONTENT_TOOL` | `True` | Manage GitHub contents. Action-routed methods: `create`, `delete`, `get`, `update`. |
|
|
61
|
+
| **Branch** | `BRANCH_TOOL` | `True` | Manage GitHub branches. Action-routed methods: `create`, `delete`, `get`, `list`. |
|
|
62
|
+
| **Commit** | `COMMIT_TOOL` | `True` | Manage GitHub commits. Action-routed methods: `get`, `list`. |
|
|
63
|
+
| **Search** | `SEARCH_TOOL` | `True` | Search GitHub repositories, issues, or code. Action-routed methods: `code`, `issues`, `repositories`. |
|
|
64
|
+
| **Org** | `ORG_TOOL` | `True` | Manage GitHub organizations. Action-routed methods: `members`, `repos`, `teams`. |
|
|
65
|
+
| **Collaborator** | `COLLABORATOR_TOOL` | `True` | Manage repository collaborators. Action-routed methods: `add`, `list`, `remove`. |
|
|
66
|
+
| **Action** | `ACTION_TOOL` | `True` | Manage GitHub actions workflows and runs. Action-routed methods: `cancel`, `delete_run`, `get_run`, `list_runs`, `list_workflows`, `rerun`, `trigger_dispatch`. |
|
|
67
|
+
| **Release** | `RELEASE_TOOL` | `True` | Manage repository releases. Action-routed methods: `create`, `delete`, `get`, `list`, `update`. |
|
|
68
|
+
|
|
69
|
+
Detailed tool schemas, parameter shapes, and validation constraints are preserved in [docs/mcp.md](docs/mcp.md).
|
|
70
|
+
|
|
71
|
+
### Dynamic Tool Selection & Visibility
|
|
72
|
+
|
|
73
|
+
This MCP server supports dynamic toolset selection and visibility filtering at runtime. This allows you to restrict the set of exposed tools in order to prevent blowing up the LLM's context window.
|
|
74
|
+
|
|
75
|
+
You can configure tool filtering via multiple input channels:
|
|
76
|
+
|
|
77
|
+
- **CLI Arguments:** Pass `--tools` or `--toolsets` (or their disabled counterparts `--disabled-tools` and `--disabled-toolsets`) during startup.
|
|
78
|
+
- **Environment Variables:** Define standard environment variables:
|
|
79
|
+
- `MCP_ENABLED_TOOLS` / `MCP_DISABLED_TOOLS`
|
|
80
|
+
- `MCP_ENABLED_TAGS` / `MCP_DISABLED_TAGS`
|
|
81
|
+
- **HTTP SSE Request Headers:** Pass custom headers during transport initialization:
|
|
82
|
+
- `x-mcp-enabled-tools` / `x-mcp-disabled-tools`
|
|
83
|
+
- `x-mcp-enabled-tags` / `x-mcp-disabled-tags`
|
|
84
|
+
- **HTTP SSE Request Query Parameters:** Append query parameters directly to your transport connection URL:
|
|
85
|
+
- `?tools=tool1,tool2`
|
|
86
|
+
- `?tags=tag1`
|
|
87
|
+
|
|
88
|
+
When query strings or parameters are supplied, an LLM-free **Knowledge Graph resolution layer** (using `DynamicToolOrchestrator`) matches query intents against known tool tags, names, or descriptions, with safe fallback and automated 24-hour background cache refreshing.
|
|
63
89
|
|
|
64
|
-
|
|
90
|
+
---
|
|
65
91
|
|
|
66
92
|
### MCP Configuration Examples
|
|
67
93
|
|
|
@@ -235,7 +261,7 @@ services:
|
|
|
235
261
|
|
|
236
262
|
```
|
|
237
263
|
|
|
238
|
-
Detailed graph node architecture explanations, custom skill configurations, and agentic trace guides are available in [docs/agent.md](
|
|
264
|
+
Detailed graph node architecture explanations, custom skill configurations, and agentic trace guides are available in [docs/agent.md](docs/agent.md).
|
|
239
265
|
|
|
240
266
|
---
|
|
241
267
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# github_agent.api package
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
5
|
+
from typing import TypeVar
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
import urllib3
|
|
9
|
+
from agent_utilities.base_utilities import get_logger
|
|
10
|
+
from agent_utilities.exceptions import (
|
|
11
|
+
AuthError,
|
|
12
|
+
MissingParameterError,
|
|
13
|
+
UnauthorizedError,
|
|
14
|
+
)
|
|
15
|
+
from pydantic import BaseModel
|
|
16
|
+
|
|
17
|
+
logger = get_logger(__name__)
|
|
18
|
+
T = TypeVar("T", bound=BaseModel)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BaseApiClient:
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
url: str | None = "https://api.github.com",
|
|
25
|
+
token: str | None = None,
|
|
26
|
+
proxies: dict | None = None,
|
|
27
|
+
verify: bool = True,
|
|
28
|
+
debug: bool = False,
|
|
29
|
+
):
|
|
30
|
+
if debug:
|
|
31
|
+
logger.setLevel(logging.DEBUG)
|
|
32
|
+
else:
|
|
33
|
+
logger.setLevel(logging.ERROR)
|
|
34
|
+
|
|
35
|
+
if url is None:
|
|
36
|
+
raise MissingParameterError
|
|
37
|
+
|
|
38
|
+
self._session = requests.Session()
|
|
39
|
+
self.url = url.rstrip("/")
|
|
40
|
+
self.headers = {
|
|
41
|
+
"Accept": "application/vnd.github+json",
|
|
42
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
43
|
+
}
|
|
44
|
+
self.verify = verify
|
|
45
|
+
self.proxies = proxies
|
|
46
|
+
self.debug = debug
|
|
47
|
+
|
|
48
|
+
if self.verify is False:
|
|
49
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
50
|
+
|
|
51
|
+
if token:
|
|
52
|
+
self.headers["Authorization"] = f"Bearer {token}"
|
|
53
|
+
else:
|
|
54
|
+
logger.warning("No token provided for GitHub API")
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
response = self._session.get(
|
|
58
|
+
url=f"{self.url}/user",
|
|
59
|
+
headers=self.headers,
|
|
60
|
+
verify=self.verify,
|
|
61
|
+
proxies=self.proxies,
|
|
62
|
+
)
|
|
63
|
+
if response.status_code in (401, 403):
|
|
64
|
+
logger.error(f"Authentication Error: {response.text}")
|
|
65
|
+
raise AuthError if response.status_code == 401 else UnauthorizedError
|
|
66
|
+
except requests.exceptions.RequestException as e:
|
|
67
|
+
logger.error(f"Connection Error: {str(e)}")
|
|
68
|
+
|
|
69
|
+
def _fetch_next_page(
|
|
70
|
+
self, endpoint: str, model: T, header: dict, page: int
|
|
71
|
+
) -> list[dict]:
|
|
72
|
+
"""Fetch a single page of data from the specified endpoint"""
|
|
73
|
+
model.page = page # type: ignore[attr-defined]
|
|
74
|
+
model.model_post_init(None)
|
|
75
|
+
response = self._session.get(
|
|
76
|
+
url=f"{self.url}{endpoint}" if endpoint.startswith("/") else endpoint,
|
|
77
|
+
params=model.api_parameters, # type: ignore[attr-defined]
|
|
78
|
+
headers=header,
|
|
79
|
+
verify=self.verify,
|
|
80
|
+
proxies=self.proxies,
|
|
81
|
+
)
|
|
82
|
+
response.raise_for_status()
|
|
83
|
+
page_data = response.json()
|
|
84
|
+
return page_data if isinstance(page_data, list) else []
|
|
85
|
+
|
|
86
|
+
def _get_total_pages(self, response: requests.Response) -> int:
|
|
87
|
+
"""Extract total pages from GitHub Link header"""
|
|
88
|
+
link = response.headers.get("Link")
|
|
89
|
+
if not link:
|
|
90
|
+
return 1
|
|
91
|
+
|
|
92
|
+
last_match = re.search(r'page=(\d+)>; rel="last"', link)
|
|
93
|
+
if last_match:
|
|
94
|
+
return int(last_match.group(1))
|
|
95
|
+
return 1
|
|
96
|
+
|
|
97
|
+
def _fetch_all_pages(
|
|
98
|
+
self, endpoint: str, model: T
|
|
99
|
+
) -> tuple[requests.Response, list[dict]]:
|
|
100
|
+
"""Generic method to fetch all pages with parallelization if possible"""
|
|
101
|
+
all_data = []
|
|
102
|
+
|
|
103
|
+
initial_url = f"{self.url}{endpoint}" if endpoint.startswith("/") else endpoint
|
|
104
|
+
|
|
105
|
+
response = self._session.get(
|
|
106
|
+
url=initial_url,
|
|
107
|
+
params=model.api_parameters, # type: ignore[attr-defined]
|
|
108
|
+
headers=self.headers,
|
|
109
|
+
verify=self.verify,
|
|
110
|
+
proxies=self.proxies,
|
|
111
|
+
)
|
|
112
|
+
response.raise_for_status()
|
|
113
|
+
initial_data = response.json()
|
|
114
|
+
|
|
115
|
+
if isinstance(initial_data, list):
|
|
116
|
+
all_data.extend(initial_data)
|
|
117
|
+
else:
|
|
118
|
+
return response, [initial_data]
|
|
119
|
+
|
|
120
|
+
total_pages = self._get_total_pages(response)
|
|
121
|
+
|
|
122
|
+
max_pages = getattr(model, "max_pages", total_pages)
|
|
123
|
+
if not max_pages or max_pages == 0 or max_pages > total_pages:
|
|
124
|
+
max_pages = total_pages
|
|
125
|
+
model.max_pages = total_pages # type: ignore[attr-defined]
|
|
126
|
+
|
|
127
|
+
if max_pages > 1:
|
|
128
|
+
with ThreadPoolExecutor(max_workers=5) as executor:
|
|
129
|
+
futures = []
|
|
130
|
+
for page in range(2, max_pages + 1):
|
|
131
|
+
futures.append(
|
|
132
|
+
executor.submit(
|
|
133
|
+
self._fetch_next_page,
|
|
134
|
+
initial_url,
|
|
135
|
+
model,
|
|
136
|
+
self.headers,
|
|
137
|
+
page,
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
for future in as_completed(futures):
|
|
142
|
+
try:
|
|
143
|
+
all_data.extend(future.result())
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.error(f"Error fetching page: {str(e)}")
|
|
146
|
+
|
|
147
|
+
return response, all_data
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from agent_utilities.decorators import require_auth
|
|
3
|
+
from agent_utilities.exceptions import (
|
|
4
|
+
ParameterError,
|
|
5
|
+
)
|
|
6
|
+
from pydantic import ValidationError
|
|
7
|
+
|
|
8
|
+
from github_agent.api.api_client_base import BaseApiClient
|
|
9
|
+
from github_agent.github_input_models import (
|
|
10
|
+
BranchModel,
|
|
11
|
+
)
|
|
12
|
+
from github_agent.github_response_models import (
|
|
13
|
+
Branch,
|
|
14
|
+
Response,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Api(BaseApiClient):
|
|
19
|
+
@require_auth
|
|
20
|
+
def get_branches(self, **kwargs) -> Response:
|
|
21
|
+
"""List branches for a repository."""
|
|
22
|
+
model = BranchModel(**kwargs)
|
|
23
|
+
try:
|
|
24
|
+
response, data = self._fetch_all_pages(
|
|
25
|
+
f"/repos/{model.owner}/{model.repo}/branches", model
|
|
26
|
+
)
|
|
27
|
+
parsed_data = [Branch(**item) for item in data]
|
|
28
|
+
return Response(response=response, data=parsed_data)
|
|
29
|
+
except ValidationError as e:
|
|
30
|
+
raise ParameterError(f"Invalid parameters: {e.errors()}") from e
|
|
31
|
+
|
|
32
|
+
@require_auth
|
|
33
|
+
def create_branch(self, owner: str, repo: str, branch: str, ref: str) -> Response:
|
|
34
|
+
"""Create a new branch in a repository (using git ref creation)."""
|
|
35
|
+
try:
|
|
36
|
+
payload = {"ref": f"refs/heads/{branch}", "sha": ref}
|
|
37
|
+
response = self._session.post(
|
|
38
|
+
url=f"{self.url}/repos/{owner}/{repo}/git/refs",
|
|
39
|
+
json=payload,
|
|
40
|
+
headers=self.headers,
|
|
41
|
+
verify=self.verify,
|
|
42
|
+
proxies=self.proxies,
|
|
43
|
+
)
|
|
44
|
+
response.raise_for_status()
|
|
45
|
+
return Response(response=response, data=response.json())
|
|
46
|
+
except ValidationError as e:
|
|
47
|
+
raise ParameterError(f"Invalid parameters: {e.errors()}") from e
|
|
48
|
+
|
|
49
|
+
@require_auth
|
|
50
|
+
def delete_branch(self, owner: str, repo: str, branch: str) -> Response:
|
|
51
|
+
"""Delete a branch in a repository."""
|
|
52
|
+
response = self._session.delete(
|
|
53
|
+
url=f"{self.url}/repos/{owner}/{repo}/git/refs/heads/{branch}",
|
|
54
|
+
headers=self.headers,
|
|
55
|
+
verify=self.verify,
|
|
56
|
+
proxies=self.proxies,
|
|
57
|
+
)
|
|
58
|
+
response.raise_for_status()
|
|
59
|
+
return Response(response=response, data={"status": "deleted"})
|
|
60
|
+
|
|
61
|
+
@require_auth
|
|
62
|
+
def get_branch(self, owner: str, repo: str, branch: str) -> Response:
|
|
63
|
+
"""Get a single branch in a repository."""
|
|
64
|
+
response = self._session.get(
|
|
65
|
+
url=f"{self.url}/repos/{owner}/{repo}/branches/{branch}",
|
|
66
|
+
headers=self.headers,
|
|
67
|
+
verify=self.verify,
|
|
68
|
+
proxies=self.proxies,
|
|
69
|
+
)
|
|
70
|
+
response.raise_for_status()
|
|
71
|
+
try:
|
|
72
|
+
parsed_data = Branch(**response.json())
|
|
73
|
+
return Response(response=response, data=parsed_data)
|
|
74
|
+
except ValidationError as e:
|
|
75
|
+
raise ParameterError(f"Invalid parameters: {e.errors()}") from e
|
|
76
|
+
|
|
77
|
+
@require_auth
|
|
78
|
+
def get_branch_protection(self, owner: str, repo: str, branch: str) -> Response:
|
|
79
|
+
"""Get branch protection configuration."""
|
|
80
|
+
response = self._session.get(
|
|
81
|
+
url=f"{self.url}/repos/{owner}/{repo}/branches/{branch}/protection",
|
|
82
|
+
headers=self.headers,
|
|
83
|
+
verify=self.verify,
|
|
84
|
+
proxies=self.proxies,
|
|
85
|
+
)
|
|
86
|
+
response.raise_for_status()
|
|
87
|
+
return Response(response=response, data=response.json())
|
|
88
|
+
|
|
89
|
+
@require_auth
|
|
90
|
+
def update_branch_protection(
|
|
91
|
+
self, owner: str, repo: str, branch: str, protection_config: dict
|
|
92
|
+
) -> Response:
|
|
93
|
+
"""Update branch protection configuration."""
|
|
94
|
+
response = self._session.put(
|
|
95
|
+
url=f"{self.url}/repos/{owner}/{repo}/branches/{branch}/protection",
|
|
96
|
+
json=protection_config,
|
|
97
|
+
headers=self.headers,
|
|
98
|
+
verify=self.verify,
|
|
99
|
+
proxies=self.proxies,
|
|
100
|
+
)
|
|
101
|
+
response.raise_for_status()
|
|
102
|
+
return Response(response=response, data=response.json())
|
|
103
|
+
|
|
104
|
+
@require_auth
|
|
105
|
+
def delete_branch_protection(self, owner: str, repo: str, branch: str) -> Response:
|
|
106
|
+
"""Delete branch protection configuration."""
|
|
107
|
+
response = self._session.delete(
|
|
108
|
+
url=f"{self.url}/repos/{owner}/{repo}/branches/{branch}/protection",
|
|
109
|
+
headers=self.headers,
|
|
110
|
+
verify=self.verify,
|
|
111
|
+
proxies=self.proxies,
|
|
112
|
+
)
|
|
113
|
+
response.raise_for_status()
|
|
114
|
+
return Response(response=response, data={"status": "protection_deleted"})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from agent_utilities.decorators import require_auth
|
|
3
|
+
from agent_utilities.exceptions import (
|
|
4
|
+
ParameterError,
|
|
5
|
+
)
|
|
6
|
+
from pydantic import ValidationError
|
|
7
|
+
|
|
8
|
+
from github_agent.api.api_client_base import BaseApiClient
|
|
9
|
+
from github_agent.github_input_models import (
|
|
10
|
+
CommitModel,
|
|
11
|
+
)
|
|
12
|
+
from github_agent.github_response_models import (
|
|
13
|
+
Commit,
|
|
14
|
+
Response,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Api(BaseApiClient):
|
|
19
|
+
@require_auth
|
|
20
|
+
def get_commits(self, **kwargs) -> Response:
|
|
21
|
+
"""List commits for a repository."""
|
|
22
|
+
model = CommitModel(**kwargs)
|
|
23
|
+
try:
|
|
24
|
+
response, data = self._fetch_all_pages(
|
|
25
|
+
f"/repos/{model.owner}/{model.repo}/commits", model
|
|
26
|
+
)
|
|
27
|
+
parsed_data = [Commit(**item) for item in data]
|
|
28
|
+
return Response(response=response, data=parsed_data)
|
|
29
|
+
except ValidationError as e:
|
|
30
|
+
raise ParameterError(f"Invalid parameters: {e.errors()}") from e
|
|
31
|
+
|
|
32
|
+
@require_auth
|
|
33
|
+
def get_commit(self, owner: str, repo: str, sha: str) -> Response:
|
|
34
|
+
"""Get a single commit in a repository."""
|
|
35
|
+
response = self._session.get(
|
|
36
|
+
url=f"{self.url}/repos/{owner}/{repo}/commits/{sha}",
|
|
37
|
+
headers=self.headers,
|
|
38
|
+
verify=self.verify,
|
|
39
|
+
proxies=self.proxies,
|
|
40
|
+
)
|
|
41
|
+
response.raise_for_status()
|
|
42
|
+
try:
|
|
43
|
+
parsed_data = Commit(**response.json())
|
|
44
|
+
return Response(response=response, data=parsed_data)
|
|
45
|
+
except ValidationError as e:
|
|
46
|
+
raise ParameterError(f"Invalid parameters: {e.errors()}") from e
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from agent_utilities.decorators import require_auth
|
|
3
|
+
from agent_utilities.exceptions import (
|
|
4
|
+
ParameterError,
|
|
5
|
+
)
|
|
6
|
+
from pydantic import ValidationError
|
|
7
|
+
|
|
8
|
+
from github_agent.api.api_client_base import BaseApiClient
|
|
9
|
+
from github_agent.github_input_models import (
|
|
10
|
+
ContentModel,
|
|
11
|
+
)
|
|
12
|
+
from github_agent.github_response_models import (
|
|
13
|
+
Content,
|
|
14
|
+
Response,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Api(BaseApiClient):
|
|
19
|
+
@require_auth
|
|
20
|
+
def get_contents(self, **kwargs) -> Response:
|
|
21
|
+
"""Get contents of a file or directory in a repository."""
|
|
22
|
+
model = ContentModel(**kwargs)
|
|
23
|
+
try:
|
|
24
|
+
response = self._session.get(
|
|
25
|
+
url=f"{self.url}/repos/{model.owner}/{model.repo}/contents/{model.path}",
|
|
26
|
+
params=model.api_parameters,
|
|
27
|
+
headers=self.headers,
|
|
28
|
+
verify=self.verify,
|
|
29
|
+
proxies=self.proxies,
|
|
30
|
+
)
|
|
31
|
+
response.raise_for_status()
|
|
32
|
+
data = response.json()
|
|
33
|
+
parsed_data: list[Content] | Content
|
|
34
|
+
if isinstance(data, list):
|
|
35
|
+
parsed_data = [Content(**item) for item in data]
|
|
36
|
+
else:
|
|
37
|
+
parsed_data = Content(**data)
|
|
38
|
+
return Response(response=response, data=parsed_data)
|
|
39
|
+
except ValidationError as e:
|
|
40
|
+
raise ParameterError(f"Invalid parameters: {e.errors()}") from e
|
|
41
|
+
|
|
42
|
+
@require_auth
|
|
43
|
+
def create_content(
|
|
44
|
+
self, owner: str, repo: str, path: str, message: str, content: str, **kwargs
|
|
45
|
+
) -> Response:
|
|
46
|
+
"""Create a file in a repository."""
|
|
47
|
+
try:
|
|
48
|
+
payload = {"message": message, "content": content, **kwargs}
|
|
49
|
+
response = self._session.put(
|
|
50
|
+
url=f"{self.url}/repos/{owner}/{repo}/contents/{path}",
|
|
51
|
+
json=payload,
|
|
52
|
+
headers=self.headers,
|
|
53
|
+
verify=self.verify,
|
|
54
|
+
proxies=self.proxies,
|
|
55
|
+
)
|
|
56
|
+
response.raise_for_status()
|
|
57
|
+
res_json = response.json()
|
|
58
|
+
if not isinstance(res_json, dict) or "content" not in res_json:
|
|
59
|
+
raise ParameterError("Invalid parameters: missing 'content' key")
|
|
60
|
+
parsed_data = Content(**res_json["content"])
|
|
61
|
+
return Response(response=response, data=parsed_data)
|
|
62
|
+
except ValidationError as e:
|
|
63
|
+
raise ParameterError(f"Invalid parameters: {e.errors()}") from e
|
|
64
|
+
|
|
65
|
+
@require_auth
|
|
66
|
+
def update_content(
|
|
67
|
+
self,
|
|
68
|
+
owner: str,
|
|
69
|
+
repo: str,
|
|
70
|
+
path: str,
|
|
71
|
+
message: str,
|
|
72
|
+
content: str,
|
|
73
|
+
sha: str,
|
|
74
|
+
**kwargs,
|
|
75
|
+
) -> Response:
|
|
76
|
+
"""Update a file in a repository."""
|
|
77
|
+
try:
|
|
78
|
+
payload = {"message": message, "content": content, "sha": sha, **kwargs}
|
|
79
|
+
response = self._session.put(
|
|
80
|
+
url=f"{self.url}/repos/{owner}/{repo}/contents/{path}",
|
|
81
|
+
json=payload,
|
|
82
|
+
headers=self.headers,
|
|
83
|
+
verify=self.verify,
|
|
84
|
+
proxies=self.proxies,
|
|
85
|
+
)
|
|
86
|
+
response.raise_for_status()
|
|
87
|
+
res_json = response.json()
|
|
88
|
+
if not isinstance(res_json, dict) or "content" not in res_json:
|
|
89
|
+
raise ParameterError("Invalid parameters: missing 'content' key")
|
|
90
|
+
parsed_data = Content(**res_json["content"])
|
|
91
|
+
return Response(response=response, data=parsed_data)
|
|
92
|
+
except ValidationError as e:
|
|
93
|
+
raise ParameterError(f"Invalid parameters: {e.errors()}") from e
|
|
94
|
+
|
|
95
|
+
@require_auth
|
|
96
|
+
def delete_content(
|
|
97
|
+
self, owner: str, repo: str, path: str, message: str, sha: str, **kwargs
|
|
98
|
+
) -> Response:
|
|
99
|
+
"""Delete a file in a repository."""
|
|
100
|
+
try:
|
|
101
|
+
payload = {"message": message, "sha": sha, **kwargs}
|
|
102
|
+
response = self._session.delete(
|
|
103
|
+
url=f"{self.url}/repos/{owner}/{repo}/contents/{path}",
|
|
104
|
+
json=payload,
|
|
105
|
+
headers=self.headers,
|
|
106
|
+
verify=self.verify,
|
|
107
|
+
proxies=self.proxies,
|
|
108
|
+
)
|
|
109
|
+
response.raise_for_status()
|
|
110
|
+
return Response(response=response, data=response.json())
|
|
111
|
+
except ValidationError as e:
|
|
112
|
+
raise ParameterError(f"Invalid parameters: {e.errors()}") from e
|