agentr 0.1.6__py3-none-any.whl → 0.1.8__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/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
- def main():
1
+ def main():
2
2
  print("hello")
agentr/application.py CHANGED
@@ -1,57 +1,94 @@
1
- from abc import ABC, abstractmethod
2
- from agentr.integration import Integration
3
- import httpx
4
-
5
- class Application(ABC):
6
- """
7
- Application is collection of tools that can be used by an agent.
8
- """
9
- def __init__(self, name: str, **kwargs):
10
- self.name = name
11
-
12
- @abstractmethod
13
- def list_tools(self):
14
- pass
15
-
16
-
17
- class APIApplication(Application):
18
- """
19
- APIApplication is an application that uses an API to interact with the world.
20
- """
21
- def __init__(self, name: str, integration: Integration = None, **kwargs):
22
- super().__init__(name, **kwargs)
23
- self.integration = integration
24
-
25
- def _get_headers(self):
26
- return {}
27
-
28
- def _get(self, url, params=None):
29
- headers = self._get_headers()
30
- response = httpx.get(url, headers=headers, params=params)
31
- response.raise_for_status()
32
- return response
33
-
34
- def _post(self, url, data):
35
- headers = self._get_headers()
36
- response = httpx.post(url, headers=headers, json=data)
37
- response.raise_for_status()
38
- return response
39
-
40
- def _put(self, url, data):
41
- headers = self._get_headers()
42
- response = httpx.put(url, headers=headers, json=data)
43
- response.raise_for_status()
44
- return response
45
-
46
- def _delete(self, url):
47
- headers = self._get_headers()
48
- response = httpx.delete(url, headers=headers)
49
- response.raise_for_status()
50
- return response
51
-
52
- def validate(self):
53
- pass
54
-
55
- @abstractmethod
56
- def list_tools(self):
1
+ from abc import ABC, abstractmethod
2
+
3
+ from loguru import logger
4
+ from agentr.exceptions import NotAuthorizedError
5
+ from agentr.integration import Integration
6
+ import httpx
7
+
8
+ class Application(ABC):
9
+ """
10
+ Application is collection of tools that can be used by an agent.
11
+ """
12
+ def __init__(self, name: str, **kwargs):
13
+ self.name = name
14
+
15
+ @abstractmethod
16
+ def list_tools(self):
17
+ pass
18
+
19
+
20
+ class APIApplication(Application):
21
+ """
22
+ APIApplication is an application that uses an API to interact with the world.
23
+ """
24
+ def __init__(self, name: str, integration: Integration = None, **kwargs):
25
+ super().__init__(name, **kwargs)
26
+ self.integration = integration
27
+
28
+ def _get_headers(self):
29
+ return {}
30
+
31
+ def _get(self, url, params=None):
32
+ try:
33
+ headers = self._get_headers()
34
+ response = httpx.get(url, headers=headers, params=params)
35
+ response.raise_for_status()
36
+ return response
37
+ except NotAuthorizedError as e:
38
+ logger.warning(f"Authorization needed: {e.message}")
39
+ raise e
40
+ except Exception as e:
41
+ logger.error(f"Error getting {url}: {e}")
42
+ raise e
43
+
44
+
45
+ def _post(self, url, data):
46
+ try:
47
+ headers = self._get_headers()
48
+ response = httpx.post(url, headers=headers, json=data)
49
+ response.raise_for_status()
50
+ return response
51
+ except NotAuthorizedError as e:
52
+ logger.warning(f"Authorization needed: {e.message}")
53
+ raise e
54
+ except httpx.HTTPStatusError as e:
55
+ if e.response.status_code == 429:
56
+ return e.response.text or "Rate limit exceeded. Please try again later."
57
+ else:
58
+ raise e
59
+ except Exception as e:
60
+ logger.error(f"Error posting {url}: {e}")
61
+ raise e
62
+
63
+ def _put(self, url, data):
64
+ try:
65
+ headers = self._get_headers()
66
+ response = httpx.put(url, headers=headers, json=data)
67
+ response.raise_for_status()
68
+ return response
69
+ except NotAuthorizedError as e:
70
+ logger.warning(f"Authorization needed: {e.message}")
71
+ raise e
72
+ except Exception as e:
73
+ logger.error(f"Error posting {url}: {e}")
74
+ raise e
75
+
76
+ def _delete(self, url):
77
+ try:
78
+ headers = self._get_headers()
79
+ response = httpx.delete(url, headers=headers)
80
+ response.raise_for_status()
81
+ return response
82
+ except NotAuthorizedError as e:
83
+ logger.warning(f"Authorization needed: {e.message}")
84
+ raise e
85
+ except Exception as e:
86
+ logger.error(f"Error posting {url}: {e}")
87
+ raise e
88
+
89
+ def validate(self):
90
+ pass
91
+
92
+ @abstractmethod
93
+ def list_tools(self):
57
94
  pass
@@ -0,0 +1,27 @@
1
+ from agentr.applications.zenquotes.app import ZenQuoteApp
2
+ from agentr.applications.tavily.app import TavilyApp
3
+ from agentr.applications.github.app import GithubApp
4
+ from agentr.applications.google_calendar.app import GoogleCalendarApp
5
+ from agentr.applications.google_mail.app import GmailApp
6
+ from agentr.applications.resend.app import ResendApp
7
+ from agentr.applications.reddit.app import RedditApp
8
+
9
+ def app_from_name(name: str):
10
+ name = name.lower().strip()
11
+ name = name.replace(" ", "-")
12
+ if name == "zenquotes":
13
+ return ZenQuoteApp
14
+ elif name == "tavily":
15
+ return TavilyApp
16
+ elif name == "github":
17
+ return GithubApp
18
+ elif name == "google-calendar":
19
+ return GoogleCalendarApp
20
+ elif name == "google-mail":
21
+ return GmailApp
22
+ elif name == "resend":
23
+ return ResendApp
24
+ elif name == "reddit":
25
+ return RedditApp
26
+ else:
27
+ raise ValueError(f"App {name} not found")
@@ -1,56 +1,319 @@
1
- from agentr.integration import Integration
2
- from agentr.application import APIApplication
3
- from loguru import logger
4
- from agentr.exceptions import NotAuthorizedError
5
-
6
- class GithubApp(APIApplication):
7
- def __init__(self, integration: Integration) -> None:
8
- super().__init__(name="github", integration=integration)
9
-
10
- def _get_headers(self):
11
- if not self.integration:
12
- raise ValueError("Integration not configured")
13
- credentials = self.integration.get_credentials()
14
- if not credentials:
15
- logger.warning("No credentials found")
16
- action = self.integration.authorize()
17
- raise NotAuthorizedError(action)
18
- if "headers" in credentials:
19
- return credentials["headers"]
20
- return {
21
- "Authorization": f"Bearer {credentials['access_token']}",
22
- "Accept": "application/vnd.github.v3+json"
23
- }
24
-
25
-
26
- def star_repository(self, repo_full_name: str) -> str:
27
- """Star a GitHub repository
28
-
29
- Args:
30
- repo_full_name: The full name of the repository (e.g. 'owner/repo')
31
-
32
- Returns:
33
-
34
- A confirmation message
35
- """
36
- try:
37
- url = f"https://api.github.com/user/starred/{repo_full_name}"
38
- response = self._put(url, data={})
39
-
40
- if response.status_code == 204:
41
- return f"Successfully starred repository {repo_full_name}"
42
- elif response.status_code == 404:
43
- return f"Repository {repo_full_name} not found"
44
- else:
45
- logger.error(response.text)
46
- return f"Error starring repository: {response.text}"
47
- except NotAuthorizedError as e:
48
- return e.message
49
- except Exception as e:
50
- logger.error(e)
51
- raise e
52
-
53
-
54
-
55
- def list_tools(self):
56
- return [self.star_repository]
1
+ from agentr.integration import Integration
2
+ from agentr.application import APIApplication
3
+ from loguru import logger
4
+
5
+ class GithubApp(APIApplication):
6
+ def __init__(self, integration: Integration) -> None:
7
+ super().__init__(name="github", integration=integration)
8
+ self.base_api_url = "https://api.github.com/repos"
9
+
10
+ def _get_headers(self):
11
+ if not self.integration:
12
+ raise ValueError("Integration not configured")
13
+ credentials = self.integration.get_credentials()
14
+ if "headers" in credentials:
15
+ return credentials["headers"]
16
+ return {
17
+ "Authorization": f"Bearer {credentials['access_token']}",
18
+ "Accept": "application/vnd.github.v3+json"
19
+ }
20
+
21
+
22
+ def star_repository(self, repo_full_name: str) -> str:
23
+ """Star a GitHub repository
24
+
25
+ Args:
26
+ repo_full_name: The full name of the repository (e.g. 'owner/repo')
27
+
28
+ Returns:
29
+
30
+ A confirmation message
31
+ """
32
+ url = f"https://api.github.com/user/starred/{repo_full_name}"
33
+ response = self._put(url, data={})
34
+
35
+ if response.status_code == 204:
36
+ return f"Successfully starred repository {repo_full_name}"
37
+ elif response.status_code == 404:
38
+ return f"Repository {repo_full_name} not found"
39
+ else:
40
+ logger.error(response.text)
41
+ return f"Error starring repository: {response.text}"
42
+
43
+
44
+ def list_commits(self, repo_full_name: str) -> str:
45
+ """List recent commits for a GitHub repository
46
+
47
+ Args:
48
+ repo_full_name: The full name of the repository (e.g. 'owner/repo')
49
+
50
+ Returns:
51
+ A formatted list of recent commits
52
+ """
53
+ repo_full_name = repo_full_name.strip()
54
+ url = f"{self.base_api_url}/{repo_full_name}/commits"
55
+ response = self._get(url)
56
+ response.raise_for_status()
57
+
58
+ commits = response.json()
59
+ if not commits:
60
+ return f"No commits found for repository {repo_full_name}"
61
+
62
+ result = f"Recent commits for {repo_full_name}:\n\n"
63
+ for commit in commits[:12]: # Limit to 12 commits
64
+ sha = commit.get("sha", "")[:7]
65
+ message = commit.get("commit", {}).get("message", "").split('\n')[0]
66
+ author = commit.get("commit", {}).get("author", {}).get("name", "Unknown")
67
+
68
+ result += f"- {sha}: {message} (by {author})\n"
69
+
70
+ return result
71
+
72
+ def list_branches(self, repo_full_name: str) -> str:
73
+ """List branches for a GitHub repository
74
+
75
+ Args:
76
+ repo_full_name: The full name of the repository (e.g. 'owner/repo')
77
+
78
+ Returns:
79
+ A formatted list of branches
80
+ """
81
+ repo_full_name = repo_full_name.strip()
82
+ url = f"{self.base_api_url}/{repo_full_name}/branches"
83
+ response = self._get(url)
84
+ response.raise_for_status()
85
+
86
+ branches = response.json()
87
+ if not branches:
88
+ return f"No branches found for repository {repo_full_name}"
89
+
90
+ result = f"Branches for {repo_full_name}:\n\n"
91
+ for branch in branches:
92
+ branch_name = branch.get("name", "Unknown")
93
+ result += f"- {branch_name}\n"
94
+
95
+ return result
96
+
97
+ def list_pull_requests(self, repo_full_name: str, state: str = "open") -> str:
98
+ """List pull requests for a GitHub repository
99
+
100
+ Args:
101
+ repo_full_name: The full name of the repository (e.g. 'owner/repo')
102
+ state: The state of the pull requests to filter by (open, closed, or all)
103
+
104
+ Returns:
105
+ A formatted list of pull requests
106
+ """
107
+ repo_full_name = repo_full_name.strip()
108
+ url = f"{self.base_api_url}/{repo_full_name}/pulls"
109
+ params = {"state": state}
110
+ response = self._get(url, params=params)
111
+ response.raise_for_status()
112
+
113
+ pull_requests = response.json()
114
+ if not pull_requests:
115
+ return f"No pull requests found for repository {repo_full_name} with state '{state}'"
116
+
117
+ result = f"Pull requests for {repo_full_name} (State: {state}):\n\n"
118
+ for pr in pull_requests:
119
+ pr_title = pr.get("title", "No Title")
120
+ pr_number = pr.get("number", "Unknown")
121
+ pr_state = pr.get("state", "Unknown")
122
+ pr_user = pr.get("user", {}).get("login", "Unknown")
123
+
124
+ result += f"- PR #{pr_number}: {pr_title} (by {pr_user}, Status: {pr_state})\n"
125
+
126
+ return result
127
+
128
+ def list_issues(self, repo_full_name: str, per_page: int = 30, page: int = 1) -> str:
129
+ """List issues for a GitHub repository
130
+
131
+ Args:
132
+ repo_full_name: The full name of the repository (e.g. 'owner/repo')
133
+ per_page: The number of results per page (max 100)
134
+ page: The page number of the results to fetch
135
+
136
+ Returns:
137
+ A formatted list of issues
138
+ """
139
+ repo_full_name = repo_full_name.strip()
140
+ url = f"{self.base_api_url}/{repo_full_name}/issues/events"
141
+ params = {
142
+ "per_page": per_page,
143
+ "page": page
144
+ }
145
+ response = self._get(url, params=params)
146
+ response.raise_for_status()
147
+
148
+ issues = response.json()
149
+ if not issues:
150
+ return f"No issues found for repository {repo_full_name}"
151
+
152
+ result = f"Issues for {repo_full_name} (Page {page}):\n\n"
153
+ for issue in issues:
154
+ issue_title = issue.get("issue", {}).get("title", "No Title")
155
+ issue_number = issue.get("issue", {}).get("number", "Unknown")
156
+ issue_state = issue.get("issue", {}).get("state", "Unknown")
157
+ issue_user = issue.get("issue", {}).get("user", {}).get("login", "Unknown")
158
+
159
+ result += f"- Issue #{issue_number}: {issue_title} (by {issue_user}, Status: {issue_state})\n"
160
+
161
+ return result
162
+
163
+ def get_pull_request(self, repo_full_name: str, pull_number: int) -> str:
164
+ """Get a specific pull request for a GitHub repository
165
+
166
+ Args:
167
+ repo_full_name: The full name of the repository (e.g. 'owner/repo')
168
+ pull_number: The number of the pull request to retrieve
169
+
170
+ Returns:
171
+ A formatted string with pull request details
172
+ """
173
+ repo_full_name = repo_full_name.strip()
174
+ url = f"{self.base_api_url}/{repo_full_name}/pulls/{pull_number}"
175
+ response = self._get(url)
176
+ response.raise_for_status()
177
+
178
+ pr = response.json()
179
+ pr_title = pr.get("title", "No Title")
180
+ pr_number = pr.get("number", "Unknown")
181
+ pr_state = pr.get("state", "Unknown")
182
+ pr_user = pr.get("user", {}).get("login", "Unknown")
183
+ pr_body = pr.get("body", "No description provided.")
184
+
185
+ result = (
186
+ f"Pull Request #{pr_number}: {pr_title}\n"
187
+ f"Created by: {pr_user}\n"
188
+ f"Status: {pr_state}\n"
189
+ f"Description: {pr_body}\n"
190
+ )
191
+
192
+ return result
193
+
194
+ def create_pull_request(self, repo_full_name: str, title: str, head: str, base: str, body: str = "",
195
+ maintainer_can_modify: bool = True, draft: bool = False) -> str:
196
+ """Create a new pull request for a GitHub repository
197
+
198
+ Args:
199
+ repo_full_name: The full name of the repository (e.g. 'owner/repo')
200
+ title: The title of the new pull request
201
+ head: The name of the branch where your changes are implemented
202
+ base: The name of the branch you want the changes pulled into
203
+ body: The contents of the pull request
204
+ maintainer_can_modify: Indicates whether maintainers can modify the pull request
205
+ draft: Indicates whether the pull request is a draft
206
+
207
+ Returns:
208
+ A confirmation message with the new pull request details
209
+ """
210
+ repo_full_name = repo_full_name.strip()
211
+ url = f"{self.base_api_url}/{repo_full_name}/pulls"
212
+
213
+ pull_request_data = {
214
+ "title": title,
215
+ "head": head,
216
+ "base": base,
217
+ "body": body,
218
+ "maintainer_can_modify": maintainer_can_modify,
219
+ "draft": draft
220
+ }
221
+
222
+ response = self._post(url, pull_request_data)
223
+ response.raise_for_status()
224
+
225
+ pr = response.json()
226
+ pr_number = pr.get("number", "Unknown")
227
+ pr_url = pr.get("html_url", "")
228
+
229
+ return f"Successfully created pull request #{pr_number}:\n" \
230
+ f"Title: {title}\n" \
231
+ f"From: {head} → To: {base}\n" \
232
+ f"URL: {pr_url}"
233
+
234
+ def create_issue(self, repo_full_name: str, title: str, body: str = "", labels = None) -> str:
235
+ """Create a new issue in a GitHub repository
236
+
237
+ Args:
238
+ repo_full_name: The full name of the repository (e.g. 'owner/repo')
239
+ title: The title of the issue
240
+ body: The contents of the issue
241
+ labels: Labels to associate with this issue. Enter as a comma-separated string
242
+ (e.g. "bug,enhancement,documentation").
243
+ NOTE: Only users with push access can set labels for new issues.
244
+ Labels are silently dropped otherwise.
245
+
246
+ Returns:
247
+ A confirmation message with the new issue details
248
+ """
249
+ repo_full_name = repo_full_name.strip()
250
+ url = f"{self.base_api_url}/{repo_full_name}/issues"
251
+
252
+ issue_data = {
253
+ "title": title,
254
+ "body": body
255
+ }
256
+
257
+ if labels:
258
+ if isinstance(labels, str):
259
+ labels_list = [label.strip() for label in labels.split(",") if label.strip()]
260
+ issue_data["labels"] = labels_list
261
+ else:
262
+ issue_data["labels"] = labels
263
+
264
+ response = self._post(url, issue_data)
265
+ response.raise_for_status()
266
+
267
+ issue = response.json()
268
+ issue_number = issue.get("number", "Unknown")
269
+ issue_url = issue.get("html_url", "")
270
+
271
+ return f"Successfully created issue #{issue_number}:\n" \
272
+ f"Title: {title}\n" \
273
+ f"URL: {issue_url}"
274
+
275
+ def list_repo_activities(self, repo_full_name: str, direction: str = "desc", per_page: int = 30) -> str:
276
+ """List activities for a GitHub repository
277
+
278
+ Args:
279
+ repo_full_name: The full name of the repository (e.g. 'owner/repo')
280
+ direction: The direction to sort the results by (asc or desc). Default: desc
281
+ per_page: The number of results per page (max 100). Default: 30
282
+
283
+ Returns:
284
+ A formatted list of repository activities
285
+ """
286
+ repo_full_name = repo_full_name.strip()
287
+ url = f"{self.base_api_url}/{repo_full_name}/activity"
288
+
289
+ # Build query parameters
290
+ params = {
291
+ "direction": direction,
292
+ "per_page": per_page
293
+ }
294
+
295
+ response = self._get(url, params=params)
296
+ response.raise_for_status()
297
+
298
+ activities = response.json()
299
+ if not activities:
300
+ return f"No activities found for repository {repo_full_name}"
301
+
302
+ result = f"Repository activities for {repo_full_name}:\n\n"
303
+
304
+ for activity in activities:
305
+ # Extract common fields
306
+ timestamp = activity.get("timestamp", "Unknown time")
307
+ actor_name = "Unknown user"
308
+ if "actor" in activity and activity["actor"]:
309
+ actor_name = activity["actor"].get("login", "Unknown user")
310
+
311
+ # Create a simple description of the activity
312
+ result += f"- {actor_name} performed an activity at {timestamp}\n"
313
+
314
+ return result
315
+
316
+ def list_tools(self):
317
+ return [self.star_repository, self.list_commits, self.list_branches,
318
+ self.list_pull_requests, self.list_issues, self.get_pull_request,
319
+ self.create_pull_request, self.create_issue, self.list_repo_activities]