noesium 0.1.0__py3-none-any.whl → 0.2.0__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.
- noesium/agents/askura_agent/__init__.py +22 -0
- noesium/agents/askura_agent/askura_agent.py +480 -0
- noesium/agents/askura_agent/conversation.py +164 -0
- noesium/agents/askura_agent/extractor.py +175 -0
- noesium/agents/askura_agent/memory.py +14 -0
- noesium/agents/askura_agent/models.py +239 -0
- noesium/agents/askura_agent/prompts.py +202 -0
- noesium/agents/askura_agent/reflection.py +234 -0
- noesium/agents/askura_agent/summarizer.py +30 -0
- noesium/agents/askura_agent/utils.py +6 -0
- noesium/agents/deep_research/__init__.py +13 -0
- noesium/agents/deep_research/agent.py +398 -0
- noesium/agents/deep_research/prompts.py +84 -0
- noesium/agents/deep_research/schemas.py +42 -0
- noesium/agents/deep_research/state.py +54 -0
- noesium/agents/search/__init__.py +5 -0
- noesium/agents/search/agent.py +474 -0
- noesium/agents/search/state.py +28 -0
- noesium/core/__init__.py +1 -1
- noesium/core/agent/base.py +10 -2
- noesium/core/goalith/decomposer/llm_decomposer.py +1 -1
- noesium/core/llm/__init__.py +1 -1
- noesium/core/llm/base.py +2 -2
- noesium/core/llm/litellm.py +42 -21
- noesium/core/llm/llamacpp.py +25 -4
- noesium/core/llm/ollama.py +43 -22
- noesium/core/llm/openai.py +25 -5
- noesium/core/llm/openrouter.py +1 -1
- noesium/core/toolify/base.py +9 -2
- noesium/core/toolify/config.py +2 -2
- noesium/core/toolify/registry.py +21 -5
- noesium/core/tracing/opik_tracing.py +7 -7
- noesium/core/vector_store/__init__.py +2 -2
- noesium/core/vector_store/base.py +1 -1
- noesium/core/vector_store/pgvector.py +10 -13
- noesium/core/vector_store/weaviate.py +2 -1
- noesium/toolkits/__init__.py +1 -0
- noesium/toolkits/arxiv_toolkit.py +310 -0
- noesium/toolkits/audio_aliyun_toolkit.py +441 -0
- noesium/toolkits/audio_toolkit.py +370 -0
- noesium/toolkits/bash_toolkit.py +332 -0
- noesium/toolkits/document_toolkit.py +454 -0
- noesium/toolkits/file_edit_toolkit.py +552 -0
- noesium/toolkits/github_toolkit.py +395 -0
- noesium/toolkits/gmail_toolkit.py +575 -0
- noesium/toolkits/image_toolkit.py +425 -0
- noesium/toolkits/memory_toolkit.py +398 -0
- noesium/toolkits/python_executor_toolkit.py +334 -0
- noesium/toolkits/search_toolkit.py +451 -0
- noesium/toolkits/serper_toolkit.py +623 -0
- noesium/toolkits/tabular_data_toolkit.py +537 -0
- noesium/toolkits/user_interaction_toolkit.py +365 -0
- noesium/toolkits/video_toolkit.py +168 -0
- noesium/toolkits/wikipedia_toolkit.py +420 -0
- {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/METADATA +56 -48
- {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/RECORD +59 -23
- {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/licenses/LICENSE +1 -1
- {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/WHEEL +0 -0
- {noesium-0.1.0.dist-info → noesium-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GitHub toolkit for repository information and operations.
|
|
3
|
+
|
|
4
|
+
Provides tools for interacting with GitHub repositories through the GitHub API,
|
|
5
|
+
including repository information retrieval, file operations, and more.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from typing import Callable, Dict, Optional
|
|
10
|
+
from urllib.parse import urlparse
|
|
11
|
+
|
|
12
|
+
import aiohttp
|
|
13
|
+
|
|
14
|
+
from noesium.core.toolify.base import AsyncBaseToolkit
|
|
15
|
+
from noesium.core.toolify.config import ToolkitConfig
|
|
16
|
+
from noesium.core.toolify.registry import register_toolkit
|
|
17
|
+
from noesium.core.utils.logging import get_logger
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@register_toolkit("github")
|
|
23
|
+
class GitHubToolkit(AsyncBaseToolkit):
|
|
24
|
+
"""
|
|
25
|
+
Toolkit for GitHub repository operations.
|
|
26
|
+
|
|
27
|
+
This toolkit provides functionality for:
|
|
28
|
+
- Repository information retrieval
|
|
29
|
+
- File content access
|
|
30
|
+
- Repository statistics
|
|
31
|
+
- Issue and PR information
|
|
32
|
+
- User and organization data
|
|
33
|
+
|
|
34
|
+
Features:
|
|
35
|
+
- GitHub API v4 (GraphQL) and REST API support
|
|
36
|
+
- Authentication with personal access tokens
|
|
37
|
+
- Rate limiting awareness
|
|
38
|
+
- Comprehensive error handling
|
|
39
|
+
- Repository URL parsing and validation
|
|
40
|
+
|
|
41
|
+
Required configuration:
|
|
42
|
+
- GITHUB_TOKEN: GitHub personal access token for API access
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, config: ToolkitConfig = None):
|
|
46
|
+
"""
|
|
47
|
+
Initialize the GitHub toolkit.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
config: Toolkit configuration containing GitHub token and settings
|
|
51
|
+
"""
|
|
52
|
+
super().__init__(config)
|
|
53
|
+
|
|
54
|
+
# Get GitHub token from config or environment
|
|
55
|
+
self.github_token = self.config.config.get("GITHUB_TOKEN") or os.getenv("GITHUB_TOKEN")
|
|
56
|
+
|
|
57
|
+
if not self.github_token:
|
|
58
|
+
self.logger.warning("GITHUB_TOKEN not found - API rate limits will be restricted")
|
|
59
|
+
|
|
60
|
+
# API configuration
|
|
61
|
+
self.api_base_url = "https://api.github.com"
|
|
62
|
+
self.api_version = "2022-11-28"
|
|
63
|
+
|
|
64
|
+
# Request headers
|
|
65
|
+
self.headers = {
|
|
66
|
+
"Accept": "application/vnd.github.v3+json",
|
|
67
|
+
"X-GitHub-Api-Version": self.api_version,
|
|
68
|
+
"User-Agent": "noesium-github-toolkit",
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if self.github_token:
|
|
72
|
+
self.headers["Authorization"] = f"Bearer {self.github_token}"
|
|
73
|
+
|
|
74
|
+
def _parse_github_url(self, github_url: str) -> Optional[Dict[str, str]]:
|
|
75
|
+
"""
|
|
76
|
+
Parse GitHub URL to extract owner and repository name.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
github_url: GitHub repository URL
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Dictionary with owner and repo, or None if invalid
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
parsed_url = urlparse(github_url)
|
|
86
|
+
|
|
87
|
+
# Handle different GitHub URL formats
|
|
88
|
+
if parsed_url.netloc not in ["github.com", "www.github.com"]:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
path_parts = parsed_url.path.strip("/").split("/")
|
|
92
|
+
|
|
93
|
+
if len(path_parts) < 2:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
return {"owner": path_parts[0], "repo": path_parts[1]}
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
self.logger.error(f"Failed to parse GitHub URL: {e}")
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
async def _make_api_request(self, endpoint: str, params: Optional[Dict] = None) -> Dict:
|
|
103
|
+
"""
|
|
104
|
+
Make an authenticated request to the GitHub API.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
endpoint: API endpoint (without base URL)
|
|
108
|
+
params: Query parameters
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
API response as dictionary
|
|
112
|
+
"""
|
|
113
|
+
url = f"{self.api_base_url}/{endpoint.lstrip('/')}"
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
async with aiohttp.ClientSession() as session:
|
|
117
|
+
async with session.get(url, headers=self.headers, params=params) as response:
|
|
118
|
+
if response.status == 404:
|
|
119
|
+
return {"error": "Repository not found or not accessible"}
|
|
120
|
+
elif response.status == 403:
|
|
121
|
+
return {"error": "API rate limit exceeded or insufficient permissions"}
|
|
122
|
+
elif response.status != 200:
|
|
123
|
+
error_text = await response.text()
|
|
124
|
+
return {"error": f"API request failed: {response.status} - {error_text}"}
|
|
125
|
+
|
|
126
|
+
return await response.json()
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
self.logger.error(f"GitHub API request failed: {e}")
|
|
130
|
+
return {"error": f"Request failed: {str(e)}"}
|
|
131
|
+
|
|
132
|
+
async def get_repo_info(self, github_url: str) -> Dict:
|
|
133
|
+
"""
|
|
134
|
+
Get comprehensive information about a GitHub repository.
|
|
135
|
+
|
|
136
|
+
This tool retrieves detailed information about a GitHub repository including
|
|
137
|
+
statistics, metadata, and current status. It's useful for analyzing
|
|
138
|
+
repositories, checking project activity, and gathering development metrics.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
github_url: GitHub repository URL (e.g., "https://github.com/owner/repo")
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Dictionary containing repository information:
|
|
145
|
+
- name: Repository name
|
|
146
|
+
- owner: Repository owner/organization
|
|
147
|
+
- description: Repository description
|
|
148
|
+
- language: Primary programming language
|
|
149
|
+
- stars: Number of stars
|
|
150
|
+
- forks: Number of forks
|
|
151
|
+
- watchers: Number of watchers
|
|
152
|
+
- open_issues: Number of open issues
|
|
153
|
+
- license: License information
|
|
154
|
+
- created_at: Creation date
|
|
155
|
+
- updated_at: Last update date
|
|
156
|
+
- size: Repository size in KB
|
|
157
|
+
- default_branch: Default branch name
|
|
158
|
+
- topics: Repository topics/tags
|
|
159
|
+
- homepage: Project homepage URL
|
|
160
|
+
- archived: Whether the repository is archived
|
|
161
|
+
- disabled: Whether the repository is disabled
|
|
162
|
+
|
|
163
|
+
Example:
|
|
164
|
+
info = await get_repo_info("https://github.com/microsoft/vscode")
|
|
165
|
+
print(f"Stars: {info['stars']}, Language: {info['language']}")
|
|
166
|
+
"""
|
|
167
|
+
self.logger.info(f"Getting repository info for: {github_url}")
|
|
168
|
+
|
|
169
|
+
# Parse the GitHub URL
|
|
170
|
+
parsed = self._parse_github_url(github_url)
|
|
171
|
+
if not parsed:
|
|
172
|
+
return {"error": "Invalid GitHub repository URL"}
|
|
173
|
+
|
|
174
|
+
owner, repo = parsed["owner"], parsed["repo"]
|
|
175
|
+
|
|
176
|
+
# Make API request
|
|
177
|
+
endpoint = f"repos/{owner}/{repo}"
|
|
178
|
+
repo_data = await self._make_api_request(endpoint)
|
|
179
|
+
|
|
180
|
+
if "error" in repo_data:
|
|
181
|
+
return repo_data
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
# Extract license information
|
|
185
|
+
license_info = repo_data.get("license")
|
|
186
|
+
license_name = license_info.get("name") if license_info else "Not specified"
|
|
187
|
+
|
|
188
|
+
# Format the response
|
|
189
|
+
info = {
|
|
190
|
+
"name": repo_data["name"],
|
|
191
|
+
"owner": repo_data["owner"]["login"],
|
|
192
|
+
"full_name": repo_data["full_name"],
|
|
193
|
+
"description": repo_data.get("description") or "No description provided",
|
|
194
|
+
"language": repo_data.get("language") or "Not specified",
|
|
195
|
+
"stars": repo_data["stargazers_count"],
|
|
196
|
+
"forks": repo_data["forks_count"],
|
|
197
|
+
"watchers": repo_data["watchers_count"],
|
|
198
|
+
"open_issues": repo_data["open_issues_count"],
|
|
199
|
+
"license": license_name,
|
|
200
|
+
"created_at": repo_data["created_at"],
|
|
201
|
+
"updated_at": repo_data["updated_at"],
|
|
202
|
+
"pushed_at": repo_data.get("pushed_at"),
|
|
203
|
+
"size": repo_data["size"], # Size in KB
|
|
204
|
+
"default_branch": repo_data["default_branch"],
|
|
205
|
+
"topics": repo_data.get("topics", []),
|
|
206
|
+
"homepage": repo_data.get("homepage"),
|
|
207
|
+
"html_url": repo_data["html_url"],
|
|
208
|
+
"clone_url": repo_data["clone_url"],
|
|
209
|
+
"archived": repo_data["archived"],
|
|
210
|
+
"disabled": repo_data["disabled"],
|
|
211
|
+
"private": repo_data["private"],
|
|
212
|
+
"has_issues": repo_data["has_issues"],
|
|
213
|
+
"has_projects": repo_data["has_projects"],
|
|
214
|
+
"has_wiki": repo_data["has_wiki"],
|
|
215
|
+
"has_pages": repo_data["has_pages"],
|
|
216
|
+
"has_downloads": repo_data["has_downloads"],
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
self.logger.info(f"Retrieved info for {info['full_name']} ({info['stars']} stars)")
|
|
220
|
+
return info
|
|
221
|
+
|
|
222
|
+
except KeyError as e:
|
|
223
|
+
return {"error": f"Unexpected API response format: missing {e}"}
|
|
224
|
+
|
|
225
|
+
async def get_repo_contents(self, github_url: str, path: str = "") -> Dict:
|
|
226
|
+
"""
|
|
227
|
+
Get the contents of a directory or file in a GitHub repository.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
github_url: GitHub repository URL
|
|
231
|
+
path: Path within the repository (empty for root directory)
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Dictionary with file/directory information or file content
|
|
235
|
+
"""
|
|
236
|
+
self.logger.info(f"Getting contents for {github_url} at path: {path}")
|
|
237
|
+
|
|
238
|
+
parsed = self._parse_github_url(github_url)
|
|
239
|
+
if not parsed:
|
|
240
|
+
return {"error": "Invalid GitHub repository URL"}
|
|
241
|
+
|
|
242
|
+
owner, repo = parsed["owner"], parsed["repo"]
|
|
243
|
+
|
|
244
|
+
# Make API request
|
|
245
|
+
endpoint = f"repos/{owner}/{repo}/contents/{path}"
|
|
246
|
+
contents_data = await self._make_api_request(endpoint)
|
|
247
|
+
|
|
248
|
+
if "error" in contents_data:
|
|
249
|
+
return contents_data
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
if isinstance(contents_data, list):
|
|
253
|
+
# Directory contents
|
|
254
|
+
items = []
|
|
255
|
+
for item in contents_data:
|
|
256
|
+
items.append(
|
|
257
|
+
{
|
|
258
|
+
"name": item["name"],
|
|
259
|
+
"path": item["path"],
|
|
260
|
+
"type": item["type"], # file, dir, symlink
|
|
261
|
+
"size": item.get("size", 0),
|
|
262
|
+
"download_url": item.get("download_url"),
|
|
263
|
+
"html_url": item.get("html_url"),
|
|
264
|
+
}
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
return {"type": "directory", "path": path, "items": items, "count": len(items)}
|
|
268
|
+
else:
|
|
269
|
+
# Single file
|
|
270
|
+
return {
|
|
271
|
+
"type": "file",
|
|
272
|
+
"name": contents_data["name"],
|
|
273
|
+
"path": contents_data["path"],
|
|
274
|
+
"size": contents_data["size"],
|
|
275
|
+
"encoding": contents_data.get("encoding"),
|
|
276
|
+
"content": contents_data.get("content"), # Base64 encoded
|
|
277
|
+
"download_url": contents_data.get("download_url"),
|
|
278
|
+
"html_url": contents_data.get("html_url"),
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
except (KeyError, TypeError) as e:
|
|
282
|
+
return {"error": f"Failed to parse contents: {str(e)}"}
|
|
283
|
+
|
|
284
|
+
async def get_repo_releases(self, github_url: str, limit: int = 10) -> Dict:
|
|
285
|
+
"""
|
|
286
|
+
Get recent releases for a GitHub repository.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
github_url: GitHub repository URL
|
|
290
|
+
limit: Maximum number of releases to return
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Dictionary with release information
|
|
294
|
+
"""
|
|
295
|
+
parsed = self._parse_github_url(github_url)
|
|
296
|
+
if not parsed:
|
|
297
|
+
return {"error": "Invalid GitHub repository URL"}
|
|
298
|
+
|
|
299
|
+
owner, repo = parsed["owner"], parsed["repo"]
|
|
300
|
+
|
|
301
|
+
endpoint = f"repos/{owner}/{repo}/releases"
|
|
302
|
+
params = {"per_page": min(limit, 100)}
|
|
303
|
+
|
|
304
|
+
releases_data = await self._make_api_request(endpoint, params)
|
|
305
|
+
|
|
306
|
+
if "error" in releases_data:
|
|
307
|
+
return releases_data
|
|
308
|
+
|
|
309
|
+
try:
|
|
310
|
+
releases = []
|
|
311
|
+
for release in releases_data[:limit]:
|
|
312
|
+
releases.append(
|
|
313
|
+
{
|
|
314
|
+
"tag_name": release["tag_name"],
|
|
315
|
+
"name": release.get("name") or release["tag_name"],
|
|
316
|
+
"body": release.get("body", ""),
|
|
317
|
+
"published_at": release.get("published_at"),
|
|
318
|
+
"created_at": release["created_at"],
|
|
319
|
+
"author": release["author"]["login"],
|
|
320
|
+
"prerelease": release["prerelease"],
|
|
321
|
+
"draft": release["draft"],
|
|
322
|
+
"html_url": release["html_url"],
|
|
323
|
+
"tarball_url": release["tarball_url"],
|
|
324
|
+
"zipball_url": release["zipball_url"],
|
|
325
|
+
"assets_count": len(release.get("assets", [])),
|
|
326
|
+
}
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
return {"releases": releases, "count": len(releases)}
|
|
330
|
+
|
|
331
|
+
except (KeyError, TypeError) as e:
|
|
332
|
+
return {"error": f"Failed to parse releases: {str(e)}"}
|
|
333
|
+
|
|
334
|
+
async def search_repositories(self, query: str, sort: str = "stars", limit: int = 10) -> Dict:
|
|
335
|
+
"""
|
|
336
|
+
Search for GitHub repositories.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
query: Search query (supports GitHub search syntax)
|
|
340
|
+
sort: Sort by 'stars', 'forks', 'help-wanted-issues', 'updated'
|
|
341
|
+
limit: Maximum number of results to return
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
Dictionary with search results
|
|
345
|
+
"""
|
|
346
|
+
self.logger.info(f"Searching repositories for: {query}")
|
|
347
|
+
|
|
348
|
+
endpoint = "search/repositories"
|
|
349
|
+
params = {"q": query, "sort": sort, "order": "desc", "per_page": min(limit, 100)}
|
|
350
|
+
|
|
351
|
+
search_data = await self._make_api_request(endpoint, params)
|
|
352
|
+
|
|
353
|
+
if "error" in search_data:
|
|
354
|
+
return search_data
|
|
355
|
+
|
|
356
|
+
try:
|
|
357
|
+
repositories = []
|
|
358
|
+
for repo in search_data.get("items", [])[:limit]:
|
|
359
|
+
repositories.append(
|
|
360
|
+
{
|
|
361
|
+
"name": repo["name"],
|
|
362
|
+
"full_name": repo["full_name"],
|
|
363
|
+
"owner": repo["owner"]["login"],
|
|
364
|
+
"description": repo.get("description", ""),
|
|
365
|
+
"language": repo.get("language"),
|
|
366
|
+
"stars": repo["stargazers_count"],
|
|
367
|
+
"forks": repo["forks_count"],
|
|
368
|
+
"updated_at": repo["updated_at"],
|
|
369
|
+
"html_url": repo["html_url"],
|
|
370
|
+
"topics": repo.get("topics", []),
|
|
371
|
+
}
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
"repositories": repositories,
|
|
376
|
+
"total_count": search_data.get("total_count", 0),
|
|
377
|
+
"count": len(repositories),
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
except (KeyError, TypeError) as e:
|
|
381
|
+
return {"error": f"Failed to parse search results: {str(e)}"}
|
|
382
|
+
|
|
383
|
+
async def get_tools_map(self) -> Dict[str, Callable]:
|
|
384
|
+
"""
|
|
385
|
+
Get the mapping of tool names to their implementation functions.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
Dictionary mapping tool names to callable functions
|
|
389
|
+
"""
|
|
390
|
+
return {
|
|
391
|
+
"get_repo_info": self.get_repo_info,
|
|
392
|
+
"get_repo_contents": self.get_repo_contents,
|
|
393
|
+
"get_repo_releases": self.get_repo_releases,
|
|
394
|
+
"search_repositories": self.search_repositories,
|
|
395
|
+
}
|