agentr 0.1.7__py3-none-any.whl → 0.1.9__py3-none-any.whl
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.
- agentr/application.py +40 -17
- agentr/applications/github/app.py +212 -264
- agentr/applications/google_calendar/app.py +349 -408
- agentr/applications/reddit/app.py +308 -8
- agentr/cli.py +31 -0
- agentr/integration.py +79 -3
- agentr/integrations/README.md +25 -0
- agentr/integrations/__init__.py +5 -0
- agentr/integrations/agentr.py +87 -0
- agentr/integrations/api_key.py +16 -0
- agentr/integrations/base.py +60 -0
- agentr/server.py +23 -2
- agentr/test.py +4 -29
- agentr/utils/openapi.py +113 -25
- {agentr-0.1.7.dist-info → agentr-0.1.9.dist-info}/METADATA +3 -3
- agentr-0.1.9.dist-info/RECORD +30 -0
- agentr-0.1.7.dist-info/RECORD +0 -25
- {agentr-0.1.7.dist-info → agentr-0.1.9.dist-info}/WHEEL +0 -0
- {agentr-0.1.7.dist-info → agentr-0.1.9.dist-info}/entry_points.txt +0 -0
- {agentr-0.1.7.dist-info → agentr-0.1.9.dist-info}/licenses/LICENSE +0 -0
agentr/application.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
from abc import ABC
|
2
|
-
|
1
|
+
from abc import ABC
|
3
2
|
from loguru import logger
|
4
3
|
from agentr.exceptions import NotAuthorizedError
|
5
4
|
from agentr.integration import Integration
|
@@ -11,11 +10,25 @@ class Application(ABC):
|
|
11
10
|
"""
|
12
11
|
def __init__(self, name: str, **kwargs):
|
13
12
|
self.name = name
|
13
|
+
self.tools = []
|
14
14
|
|
15
|
-
|
15
|
+
def tool(self):
|
16
|
+
"""Decorator to register a method as a tool.
|
17
|
+
|
18
|
+
This decorator adds the decorated method to the application's tools list
|
19
|
+
and returns the original method unchanged.
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
The decorated method
|
23
|
+
"""
|
24
|
+
def decorator(func):
|
25
|
+
if func not in self.tools:
|
26
|
+
self.tools.append(func)
|
27
|
+
return func
|
28
|
+
return decorator
|
29
|
+
|
16
30
|
def list_tools(self):
|
17
|
-
|
18
|
-
|
31
|
+
return self.tools
|
19
32
|
|
20
33
|
class APIApplication(Application):
|
21
34
|
"""
|
@@ -35,8 +48,8 @@ class APIApplication(Application):
|
|
35
48
|
response.raise_for_status()
|
36
49
|
return response
|
37
50
|
except NotAuthorizedError as e:
|
38
|
-
logger.warning(f"
|
39
|
-
|
51
|
+
logger.warning(f"Authorization needed: {e.message}")
|
52
|
+
raise e
|
40
53
|
except Exception as e:
|
41
54
|
logger.error(f"Error getting {url}: {e}")
|
42
55
|
raise e
|
@@ -49,8 +62,8 @@ class APIApplication(Application):
|
|
49
62
|
response.raise_for_status()
|
50
63
|
return response
|
51
64
|
except NotAuthorizedError as e:
|
52
|
-
logger.warning(f"
|
53
|
-
|
65
|
+
logger.warning(f"Authorization needed: {e.message}")
|
66
|
+
raise e
|
54
67
|
except httpx.HTTPStatusError as e:
|
55
68
|
if e.response.status_code == 429:
|
56
69
|
return e.response.text or "Rate limit exceeded. Please try again later."
|
@@ -67,8 +80,8 @@ class APIApplication(Application):
|
|
67
80
|
response.raise_for_status()
|
68
81
|
return response
|
69
82
|
except NotAuthorizedError as e:
|
70
|
-
logger.warning(f"
|
71
|
-
|
83
|
+
logger.warning(f"Authorization needed: {e.message}")
|
84
|
+
raise e
|
72
85
|
except Exception as e:
|
73
86
|
logger.error(f"Error posting {url}: {e}")
|
74
87
|
raise e
|
@@ -80,14 +93,24 @@ class APIApplication(Application):
|
|
80
93
|
response.raise_for_status()
|
81
94
|
return response
|
82
95
|
except NotAuthorizedError as e:
|
83
|
-
logger.warning(f"
|
84
|
-
|
96
|
+
logger.warning(f"Authorization needed: {e.message}")
|
97
|
+
raise e
|
85
98
|
except Exception as e:
|
86
99
|
logger.error(f"Error posting {url}: {e}")
|
100
|
+
raise e
|
101
|
+
|
102
|
+
def _patch(self, url, data):
|
103
|
+
try:
|
104
|
+
headers = self._get_headers()
|
105
|
+
response = httpx.patch(url, headers=headers, json=data)
|
106
|
+
response.raise_for_status()
|
107
|
+
return response
|
108
|
+
except NotAuthorizedError as e:
|
109
|
+
logger.warning(f"Authorization needed: {e.message}")
|
110
|
+
raise e
|
111
|
+
except Exception as e:
|
112
|
+
logger.error(f"Error patching {url}: {e}")
|
113
|
+
raise e
|
87
114
|
|
88
115
|
def validate(self):
|
89
|
-
pass
|
90
|
-
|
91
|
-
@abstractmethod
|
92
|
-
def list_tools(self):
|
93
116
|
pass
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from agentr.integration import Integration
|
2
2
|
from agentr.application import APIApplication
|
3
3
|
from loguru import logger
|
4
|
-
from
|
4
|
+
from typing import List, Dict, Any
|
5
5
|
|
6
6
|
class GithubApp(APIApplication):
|
7
7
|
def __init__(self, integration: Integration) -> None:
|
@@ -12,10 +12,6 @@ class GithubApp(APIApplication):
|
|
12
12
|
if not self.integration:
|
13
13
|
raise ValueError("Integration not configured")
|
14
14
|
credentials = self.integration.get_credentials()
|
15
|
-
if not credentials:
|
16
|
-
logger.warning("No credentials found")
|
17
|
-
action = self.integration.authorize()
|
18
|
-
raise NotAuthorizedError(action)
|
19
15
|
if "headers" in credentials:
|
20
16
|
return credentials["headers"]
|
21
17
|
return {
|
@@ -55,35 +51,24 @@ class GithubApp(APIApplication):
|
|
55
51
|
Returns:
|
56
52
|
A formatted list of recent commits
|
57
53
|
"""
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
54
|
+
repo_full_name = repo_full_name.strip()
|
55
|
+
url = f"{self.base_api_url}/{repo_full_name}/commits"
|
56
|
+
response = self._get(url)
|
57
|
+
response.raise_for_status()
|
58
|
+
|
59
|
+
commits = response.json()
|
60
|
+
if not commits:
|
61
|
+
return f"No commits found for repository {repo_full_name}"
|
62
|
+
|
63
|
+
result = f"Recent commits for {repo_full_name}:\n\n"
|
64
|
+
for commit in commits[:12]: # Limit to 12 commits
|
65
|
+
sha = commit.get("sha", "")[:7]
|
66
|
+
message = commit.get("commit", {}).get("message", "").split('\n')[0]
|
67
|
+
author = commit.get("commit", {}).get("author", {}).get("name", "Unknown")
|
62
68
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
return f"No commits found for repository {repo_full_name}"
|
67
|
-
|
68
|
-
result = f"Recent commits for {repo_full_name}:\n\n"
|
69
|
-
for commit in commits[:12]: # Limit to 12 commits
|
70
|
-
sha = commit.get("sha", "")[:7]
|
71
|
-
message = commit.get("commit", {}).get("message", "").split('\n')[0]
|
72
|
-
author = commit.get("commit", {}).get("author", {}).get("name", "Unknown")
|
73
|
-
|
74
|
-
result += f"- {sha}: {message} (by {author})\n"
|
75
|
-
|
76
|
-
return result
|
77
|
-
elif response.status_code == 404:
|
78
|
-
return f"Repository {repo_full_name} not found"
|
79
|
-
else:
|
80
|
-
logger.error(response.text)
|
81
|
-
return f"Error retrieving commits: {response.status_code} - {response.text}"
|
82
|
-
except NotAuthorizedError as e:
|
83
|
-
return e.message
|
84
|
-
except Exception as e:
|
85
|
-
logger.error(e)
|
86
|
-
return f"Error retrieving commits: {str(e)}"
|
69
|
+
result += f"- {sha}: {message} (by {author})\n"
|
70
|
+
|
71
|
+
return result
|
87
72
|
|
88
73
|
def list_branches(self, repo_full_name: str) -> str:
|
89
74
|
"""List branches for a GitHub repository
|
@@ -94,32 +79,21 @@ class GithubApp(APIApplication):
|
|
94
79
|
Returns:
|
95
80
|
A formatted list of branches
|
96
81
|
"""
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
return result
|
113
|
-
elif response.status_code == 404:
|
114
|
-
return f"Repository {repo_full_name} not found"
|
115
|
-
else:
|
116
|
-
logger.error(response.text)
|
117
|
-
return f"Error retrieving branches: {response.status_code} - {response.text}"
|
118
|
-
except NotAuthorizedError as e:
|
119
|
-
return e.message
|
120
|
-
except Exception as e:
|
121
|
-
logger.error(e)
|
122
|
-
return f"Error retrieving branches: {str(e)}"
|
82
|
+
repo_full_name = repo_full_name.strip()
|
83
|
+
url = f"{self.base_api_url}/{repo_full_name}/branches"
|
84
|
+
response = self._get(url)
|
85
|
+
response.raise_for_status()
|
86
|
+
|
87
|
+
branches = response.json()
|
88
|
+
if not branches:
|
89
|
+
return f"No branches found for repository {repo_full_name}"
|
90
|
+
|
91
|
+
result = f"Branches for {repo_full_name}:\n\n"
|
92
|
+
for branch in branches:
|
93
|
+
branch_name = branch.get("name", "Unknown")
|
94
|
+
result += f"- {branch_name}\n"
|
95
|
+
|
96
|
+
return result
|
123
97
|
|
124
98
|
def list_pull_requests(self, repo_full_name: str, state: str = "open") -> str:
|
125
99
|
"""List pull requests for a GitHub repository
|
@@ -131,83 +105,59 @@ class GithubApp(APIApplication):
|
|
131
105
|
Returns:
|
132
106
|
A formatted list of pull requests
|
133
107
|
"""
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
108
|
+
repo_full_name = repo_full_name.strip()
|
109
|
+
url = f"{self.base_api_url}/{repo_full_name}/pulls"
|
110
|
+
params = {"state": state}
|
111
|
+
response = self._get(url, params=params)
|
112
|
+
response.raise_for_status()
|
113
|
+
|
114
|
+
pull_requests = response.json()
|
115
|
+
if not pull_requests:
|
116
|
+
return f"No pull requests found for repository {repo_full_name} with state '{state}'"
|
117
|
+
|
118
|
+
result = f"Pull requests for {repo_full_name} (State: {state}):\n\n"
|
119
|
+
for pr in pull_requests:
|
120
|
+
pr_title = pr.get("title", "No Title")
|
121
|
+
pr_number = pr.get("number", "Unknown")
|
122
|
+
pr_state = pr.get("state", "Unknown")
|
123
|
+
pr_user = pr.get("user", {}).get("login", "Unknown")
|
139
124
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
return f"No pull requests found for repository {repo_full_name} with state '{state}'"
|
144
|
-
|
145
|
-
result = f"Pull requests for {repo_full_name} (State: {state}):\n\n"
|
146
|
-
for pr in pull_requests:
|
147
|
-
pr_title = pr.get("title", "No Title")
|
148
|
-
pr_number = pr.get("number", "Unknown")
|
149
|
-
pr_state = pr.get("state", "Unknown")
|
150
|
-
pr_user = pr.get("user", {}).get("login", "Unknown")
|
151
|
-
|
152
|
-
result += f"- PR #{pr_number}: {pr_title} (by {pr_user}, Status: {pr_state})\n"
|
153
|
-
|
154
|
-
return result
|
155
|
-
elif response.status_code == 404:
|
156
|
-
return f"Repository {repo_full_name} not found"
|
157
|
-
else:
|
158
|
-
logger.error(response.text)
|
159
|
-
return f"Error retrieving pull requests: {response.status_code} - {response.text}"
|
160
|
-
except NotAuthorizedError as e:
|
161
|
-
return e.message
|
162
|
-
except Exception as e:
|
163
|
-
logger.error(e)
|
164
|
-
return f"Error retrieving pull requests: {str(e)}"
|
125
|
+
result += f"- PR #{pr_number}: {pr_title} (by {pr_user}, Status: {pr_state})\n"
|
126
|
+
|
127
|
+
return result
|
165
128
|
|
166
|
-
def list_issues(self, repo_full_name: str,
|
129
|
+
def list_issues(self, repo_full_name: str, state: str = "open", assignee: str = None,
|
130
|
+
labels: str = None, per_page: int = 30, page: int = 1) -> List[Dict[str, Any]]:
|
167
131
|
"""List issues for a GitHub repository
|
168
132
|
|
169
133
|
Args:
|
170
134
|
repo_full_name: The full name of the repository (e.g. 'owner/repo')
|
135
|
+
state: State of issues to return (open, closed, all). Default: open
|
136
|
+
assignee: Filter by assignee. Use 'none' for no assignee, '*' for any assignee
|
137
|
+
labels: Comma-separated list of label names (e.g. "bug,ui,@high")
|
171
138
|
per_page: The number of results per page (max 100)
|
172
139
|
page: The page number of the results to fetch
|
173
140
|
|
174
141
|
Returns:
|
175
|
-
|
142
|
+
The complete GitHub API response
|
176
143
|
"""
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
issue_number = issue.get("issue", {}).get("number", "Unknown")
|
195
|
-
issue_state = issue.get("issue", {}).get("state", "Unknown")
|
196
|
-
issue_user = issue.get("issue", {}).get("user", {}).get("login", "Unknown")
|
197
|
-
|
198
|
-
result += f"- Issue #{issue_number}: {issue_title} (by {issue_user}, Status: {issue_state})\n"
|
199
|
-
|
200
|
-
return result
|
201
|
-
elif response.status_code == 404:
|
202
|
-
return f"Repository {repo_full_name} not found"
|
203
|
-
else:
|
204
|
-
logger.error(response.text)
|
205
|
-
return f"Error retrieving issues: {response.status_code} - {response.text}"
|
206
|
-
except NotAuthorizedError as e:
|
207
|
-
return e.message
|
208
|
-
except Exception as e:
|
209
|
-
logger.error(e)
|
210
|
-
return f"Error retrieving issues: {str(e)}"
|
144
|
+
repo_full_name = repo_full_name.strip()
|
145
|
+
url = f"{self.base_api_url}/{repo_full_name}/issues"
|
146
|
+
|
147
|
+
params = {
|
148
|
+
"state": state,
|
149
|
+
"per_page": per_page,
|
150
|
+
"page": page
|
151
|
+
}
|
152
|
+
|
153
|
+
if assignee:
|
154
|
+
params["assignee"] = assignee
|
155
|
+
if labels:
|
156
|
+
params["labels"] = labels
|
157
|
+
|
158
|
+
response = self._get(url, params=params)
|
159
|
+
response.raise_for_status()
|
160
|
+
return response.json()
|
211
161
|
|
212
162
|
def get_pull_request(self, repo_full_name: str, pull_number: int) -> str:
|
213
163
|
"""Get a specific pull request for a GitHub repository
|
@@ -219,86 +169,68 @@ class GithubApp(APIApplication):
|
|
219
169
|
Returns:
|
220
170
|
A formatted string with pull request details
|
221
171
|
"""
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
return result
|
243
|
-
elif response.status_code == 404:
|
244
|
-
return f"Pull request #{pull_number} not found in repository {repo_full_name}"
|
245
|
-
else:
|
246
|
-
logger.error(response.text)
|
247
|
-
return f"Error retrieving pull request: {response.status_code} - {response.text}"
|
248
|
-
except NotAuthorizedError as e:
|
249
|
-
return e.message
|
250
|
-
except Exception as e:
|
251
|
-
logger.error(e)
|
252
|
-
return f"Error retrieving pull request: {str(e)}"
|
172
|
+
repo_full_name = repo_full_name.strip()
|
173
|
+
url = f"{self.base_api_url}/{repo_full_name}/pulls/{pull_number}"
|
174
|
+
response = self._get(url)
|
175
|
+
response.raise_for_status()
|
176
|
+
|
177
|
+
pr = response.json()
|
178
|
+
pr_title = pr.get("title", "No Title")
|
179
|
+
pr_number = pr.get("number", "Unknown")
|
180
|
+
pr_state = pr.get("state", "Unknown")
|
181
|
+
pr_user = pr.get("user", {}).get("login", "Unknown")
|
182
|
+
pr_body = pr.get("body", "No description provided.")
|
183
|
+
|
184
|
+
result = (
|
185
|
+
f"Pull Request #{pr_number}: {pr_title}\n"
|
186
|
+
f"Created by: {pr_user}\n"
|
187
|
+
f"Status: {pr_state}\n"
|
188
|
+
f"Description: {pr_body}\n"
|
189
|
+
)
|
190
|
+
|
191
|
+
return result
|
253
192
|
|
254
|
-
def create_pull_request(self, repo_full_name: str,
|
255
|
-
|
193
|
+
def create_pull_request(self, repo_full_name: str, head: str, base: str, title: str = None,
|
194
|
+
body: str = None, issue: int = None, maintainer_can_modify: bool = True,
|
195
|
+
draft: bool = False) -> Dict[str, Any]:
|
256
196
|
"""Create a new pull request for a GitHub repository
|
257
197
|
|
258
198
|
Args:
|
259
199
|
repo_full_name: The full name of the repository (e.g. 'owner/repo')
|
260
|
-
title: The title of the new pull request
|
261
200
|
head: The name of the branch where your changes are implemented
|
262
201
|
base: The name of the branch you want the changes pulled into
|
202
|
+
title: The title of the new pull request (required if issue is not specified)
|
263
203
|
body: The contents of the pull request
|
204
|
+
issue: An issue number to convert to a pull request. If specified, the issue's
|
205
|
+
title, body, and comments will be used for the pull request
|
264
206
|
maintainer_can_modify: Indicates whether maintainers can modify the pull request
|
265
207
|
draft: Indicates whether the pull request is a draft
|
266
208
|
|
267
209
|
Returns:
|
268
|
-
|
210
|
+
The complete GitHub API response
|
269
211
|
"""
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
f"From: {head} → To: {base}\n" \
|
293
|
-
f"URL: {pr_url}"
|
294
|
-
else:
|
295
|
-
logger.error(response.text)
|
296
|
-
return f"Error creating pull request: {response.status_code} - {response.text}"
|
297
|
-
except NotAuthorizedError as e:
|
298
|
-
return e.message
|
299
|
-
except Exception as e:
|
300
|
-
logger.error(e)
|
301
|
-
return f"Error creating pull request: {str(e)}"
|
212
|
+
repo_full_name = repo_full_name.strip()
|
213
|
+
url = f"{self.base_api_url}/{repo_full_name}/pulls"
|
214
|
+
|
215
|
+
pull_request_data = {
|
216
|
+
"head": head,
|
217
|
+
"base": base,
|
218
|
+
"maintainer_can_modify": maintainer_can_modify,
|
219
|
+
"draft": draft
|
220
|
+
}
|
221
|
+
|
222
|
+
if issue is not None:
|
223
|
+
pull_request_data["issue"] = issue
|
224
|
+
else:
|
225
|
+
if title is None:
|
226
|
+
raise ValueError("Either 'title' or 'issue' must be specified")
|
227
|
+
pull_request_data["title"] = title
|
228
|
+
if body is not None:
|
229
|
+
pull_request_data["body"] = body
|
230
|
+
|
231
|
+
response = self._post(url, pull_request_data)
|
232
|
+
response.raise_for_status()
|
233
|
+
return response.json()
|
302
234
|
|
303
235
|
def create_issue(self, repo_full_name: str, title: str, body: str = "", labels = None) -> str:
|
304
236
|
"""Create a new issue in a GitHub repository
|
@@ -315,40 +247,31 @@ class GithubApp(APIApplication):
|
|
315
247
|
Returns:
|
316
248
|
A confirmation message with the new issue details
|
317
249
|
"""
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
if labels:
|
328
|
-
|
329
|
-
|
330
|
-
issue_data["labels"] = labels_list
|
331
|
-
else:
|
332
|
-
issue_data["labels"] = labels
|
333
|
-
|
334
|
-
response = self._post(url, issue_data)
|
335
|
-
|
336
|
-
if response.status_code in [200, 201]:
|
337
|
-
issue = response.json()
|
338
|
-
issue_number = issue.get("number", "Unknown")
|
339
|
-
issue_url = issue.get("html_url", "")
|
340
|
-
|
341
|
-
return f"Successfully created issue #{issue_number}:\n" \
|
342
|
-
f"Title: {title}\n" \
|
343
|
-
f"URL: {issue_url}"
|
250
|
+
repo_full_name = repo_full_name.strip()
|
251
|
+
url = f"{self.base_api_url}/{repo_full_name}/issues"
|
252
|
+
|
253
|
+
issue_data = {
|
254
|
+
"title": title,
|
255
|
+
"body": body
|
256
|
+
}
|
257
|
+
|
258
|
+
if labels:
|
259
|
+
if isinstance(labels, str):
|
260
|
+
labels_list = [label.strip() for label in labels.split(",") if label.strip()]
|
261
|
+
issue_data["labels"] = labels_list
|
344
262
|
else:
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
263
|
+
issue_data["labels"] = labels
|
264
|
+
|
265
|
+
response = self._post(url, issue_data)
|
266
|
+
response.raise_for_status()
|
267
|
+
|
268
|
+
issue = response.json()
|
269
|
+
issue_number = issue.get("number", "Unknown")
|
270
|
+
issue_url = issue.get("html_url", "")
|
271
|
+
|
272
|
+
return f"Successfully created issue #{issue_number}:\n" \
|
273
|
+
f"Title: {title}\n" \
|
274
|
+
f"URL: {issue_url}"
|
352
275
|
|
353
276
|
def list_repo_activities(self, repo_full_name: str, direction: str = "desc", per_page: int = 30) -> str:
|
354
277
|
"""List activities for a GitHub repository
|
@@ -361,48 +284,73 @@ class GithubApp(APIApplication):
|
|
361
284
|
Returns:
|
362
285
|
A formatted list of repository activities
|
363
286
|
"""
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
287
|
+
repo_full_name = repo_full_name.strip()
|
288
|
+
url = f"{self.base_api_url}/{repo_full_name}/activity"
|
289
|
+
|
290
|
+
# Build query parameters
|
291
|
+
params = {
|
292
|
+
"direction": direction,
|
293
|
+
"per_page": per_page
|
294
|
+
}
|
295
|
+
|
296
|
+
response = self._get(url, params=params)
|
297
|
+
response.raise_for_status()
|
298
|
+
|
299
|
+
activities = response.json()
|
300
|
+
if not activities:
|
301
|
+
return f"No activities found for repository {repo_full_name}"
|
302
|
+
|
303
|
+
result = f"Repository activities for {repo_full_name}:\n\n"
|
304
|
+
|
305
|
+
for activity in activities:
|
306
|
+
# Extract common fields
|
307
|
+
timestamp = activity.get("timestamp", "Unknown time")
|
308
|
+
actor_name = "Unknown user"
|
309
|
+
if "actor" in activity and activity["actor"]:
|
310
|
+
actor_name = activity["actor"].get("login", "Unknown user")
|
373
311
|
|
374
|
-
|
312
|
+
# Create a simple description of the activity
|
313
|
+
result += f"- {actor_name} performed an activity at {timestamp}\n"
|
314
|
+
|
315
|
+
return result
|
316
|
+
|
317
|
+
def update_issue(self, repo_full_name: str, issue_number: int, title: str = None,
|
318
|
+
body: str = None, assignee: str = None, state: str = None,
|
319
|
+
state_reason: str = None) -> Dict[str, Any]:
|
320
|
+
"""Update an issue in a GitHub repository
|
321
|
+
|
322
|
+
Args:
|
323
|
+
repo_full_name: The full name of the repository (e.g. 'owner/repo')
|
324
|
+
issue_number: The number that identifies the issue
|
325
|
+
title: The title of the issue
|
326
|
+
body: The contents of the issue
|
327
|
+
assignee: Username to assign to this issue
|
328
|
+
state: State of the issue (open or closed)
|
329
|
+
state_reason: Reason for state change (completed, not_planned, reopened, null)
|
375
330
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
else:
|
397
|
-
logger.error(response.text)
|
398
|
-
return f"Error retrieving activities: {response.status_code} - {response.text}"
|
399
|
-
except NotAuthorizedError as e:
|
400
|
-
return e.message
|
401
|
-
except Exception as e:
|
402
|
-
logger.error(e)
|
403
|
-
return f"Error retrieving activities: {str(e)}"
|
331
|
+
Returns:
|
332
|
+
The complete GitHub API response
|
333
|
+
"""
|
334
|
+
url = f"{self.base_api_url}/{repo_full_name}/issues/{issue_number}"
|
335
|
+
|
336
|
+
update_data = {}
|
337
|
+
if title is not None:
|
338
|
+
update_data["title"] = title
|
339
|
+
if body is not None:
|
340
|
+
update_data["body"] = body
|
341
|
+
if assignee is not None:
|
342
|
+
update_data["assignee"] = assignee
|
343
|
+
if state is not None:
|
344
|
+
update_data["state"] = state
|
345
|
+
if state_reason is not None:
|
346
|
+
update_data["state_reason"] = state_reason
|
347
|
+
|
348
|
+
response = self._patch(url, update_data)
|
349
|
+
response.raise_for_status()
|
350
|
+
return response.json()
|
404
351
|
|
405
352
|
def list_tools(self):
|
406
353
|
return [self.star_repository, self.list_commits, self.list_branches,
|
407
354
|
self.list_pull_requests, self.list_issues, self.get_pull_request,
|
408
|
-
self.create_pull_request, self.create_issue, self.
|
355
|
+
self.create_pull_request, self.create_issue, self.update_issue,
|
356
|
+
self.list_repo_activities]
|