Commit
·
1278b3f
1
Parent(s):
b4b185f
Refactor directory structure
Browse files- agent/agent_config/prompts.py +25 -0
- agent/{mistral.py → agent_config/tool_schema.py} +1 -97
- agent/core.py +78 -0
- requirements.txt +5 -0
- tools/__init__.py +4 -0
- {agent → tools}/code_index.py +2 -45
- tools/github_tools.py +47 -0
- agent/function_calling.py → tools/utils.py +36 -108
agent/agent_config/prompts.py
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
system_message = {
|
2 |
+
"role": "system",
|
3 |
+
"content": (
|
4 |
+
"You are a senior developer assistant bot for GitHub issues.\n\n"
|
5 |
+
|
6 |
+
"Your job is to respond to GitHub issues **professionally** and **helpfully**, but never repeat the issue description verbatim.\n\n"
|
7 |
+
"First, classify the issue as one of the following:\n"
|
8 |
+
"- Bug report\n"
|
9 |
+
"- Implementation question\n"
|
10 |
+
"- Feature request\n"
|
11 |
+
"- Incomplete or unclear\n\n"
|
12 |
+
|
13 |
+
"Then, based on the classification, write a clear, concise, and friendly response.\n\n"
|
14 |
+
"The comment should be well formatted and readable, using Markdown for code blocks and lists where appropriate.\n\n"
|
15 |
+
"DO NOT paste or repeat the issue description. DO NOT quote it. Respond entirely in your own words.\n"
|
16 |
+
"You can only use the following tools: fetch_github_issue, get_issue_details, retrieve_context, post_comment.\n"
|
17 |
+
"Do not attempt to use any other tools such as web_search."
|
18 |
+
"DO NOT HALLUCINATE OR MAKE UP TOOLS."
|
19 |
+
)
|
20 |
+
}
|
21 |
+
|
22 |
+
user_message = {
|
23 |
+
"role": "user",
|
24 |
+
"content": "Please suggest a fix on this issue https://github.com/aditi-dsi/testing-cryptope/issues/4."
|
25 |
+
}
|
agent/{mistral.py → agent_config/tool_schema.py}
RENAMED
@@ -1,9 +1,3 @@
|
|
1 |
-
import json
|
2 |
-
from mistralai import Mistral
|
3 |
-
from agent.function_calling import fetch_github_issue, get_issue_details, post_comment
|
4 |
-
from agent.code_index import retrieve_context
|
5 |
-
from config import MISTRAL_API_KEY
|
6 |
-
|
7 |
tools = [
|
8 |
{
|
9 |
"type": "function",
|
@@ -105,94 +99,4 @@ tools = [
|
|
105 |
},
|
106 |
},
|
107 |
},
|
108 |
-
]
|
109 |
-
|
110 |
-
names_to_functions = {
|
111 |
-
"fetch_github_issue": fetch_github_issue,
|
112 |
-
"get_issue_details": get_issue_details,
|
113 |
-
"retrieve_context": retrieve_context,
|
114 |
-
"post_comment": post_comment,
|
115 |
-
}
|
116 |
-
|
117 |
-
allowed_tools = set(names_to_functions.keys())
|
118 |
-
|
119 |
-
system_message = {
|
120 |
-
"role": "system",
|
121 |
-
"content": (
|
122 |
-
"You are a senior developer assistant bot for GitHub issues.\n\n"
|
123 |
-
|
124 |
-
"Your job is to respond to GitHub issues **professionally** and **helpfully**, but never repeat the issue description verbatim.\n\n"
|
125 |
-
"First, classify the issue as one of the following:\n"
|
126 |
-
"- Bug report\n"
|
127 |
-
"- Implementation question\n"
|
128 |
-
"- Feature request\n"
|
129 |
-
"- Incomplete or unclear\n\n"
|
130 |
-
|
131 |
-
"Then, based on the classification, write a clear, concise, and friendly response.\n\n"
|
132 |
-
"The comment should be well formatted and readable, using Markdown for code blocks and lists where appropriate.\n\n"
|
133 |
-
"DO NOT paste or repeat the issue description. DO NOT quote it. Respond entirely in your own words.\n"
|
134 |
-
"You can only use the following tools: fetch_github_issue, get_issue_details, retrieve_context, post_comment.\n"
|
135 |
-
"Do not attempt to use any other tools such as web_search."
|
136 |
-
"DO NOT HALLUCINATE OR MAKE UP TOOLS."
|
137 |
-
)
|
138 |
-
}
|
139 |
-
|
140 |
-
user_message = {
|
141 |
-
"role": "user",
|
142 |
-
"content": "Please suggest a fix on this issue https://github.com/aditi-dsi/testing-cryptope/issues/4."
|
143 |
-
}
|
144 |
-
|
145 |
-
messages = [system_message, user_message]
|
146 |
-
|
147 |
-
api_key = MISTRAL_API_KEY
|
148 |
-
model = "devstral-small-latest"
|
149 |
-
client = Mistral(api_key=api_key)
|
150 |
-
|
151 |
-
MAX_STEPS = 5
|
152 |
-
tool_calls = 0
|
153 |
-
|
154 |
-
while True:
|
155 |
-
response = client.chat.complete(
|
156 |
-
model=model,
|
157 |
-
messages=messages,
|
158 |
-
tools=tools,
|
159 |
-
tool_choice="any",
|
160 |
-
)
|
161 |
-
msg = response.choices[0].message
|
162 |
-
messages.append(msg)
|
163 |
-
|
164 |
-
if hasattr(msg, "tool_calls") and msg.tool_calls:
|
165 |
-
for tool_call in msg.tool_calls:
|
166 |
-
function_name = tool_call.function.name
|
167 |
-
function_params = json.loads(tool_call.function.arguments)
|
168 |
-
if function_name in allowed_tools:
|
169 |
-
function_result = names_to_functions[function_name](**function_params)
|
170 |
-
print(f"Agent is calling tool: {function_name}")
|
171 |
-
tool_calls += 1
|
172 |
-
messages.append({
|
173 |
-
"role": "tool",
|
174 |
-
"tool_call_id": tool_call.id,
|
175 |
-
"content": str(function_result)
|
176 |
-
})
|
177 |
-
|
178 |
-
if function_name == "post_comment":
|
179 |
-
print("OpenSorus (final): ✅ Comment posted successfully. No further action needed.")
|
180 |
-
exit(0)
|
181 |
-
|
182 |
-
else:
|
183 |
-
print(f"LLM tried to call unknown tool: {function_name}")
|
184 |
-
tool_error_msg = (
|
185 |
-
f"Error: Tool '{function_name}' is not available. "
|
186 |
-
"You can only use the following tools: fetch_github_issue, get_issue_details, post_comment."
|
187 |
-
)
|
188 |
-
messages.append({
|
189 |
-
"role": "tool",
|
190 |
-
"tool_call_id": tool_call.id,
|
191 |
-
"content": tool_error_msg
|
192 |
-
})
|
193 |
-
if tool_calls >= MAX_STEPS:
|
194 |
-
print(f"Agent stopped after {MAX_STEPS} tool calls to protect against rate limiting.")
|
195 |
-
break
|
196 |
-
else:
|
197 |
-
print("OpenSorus (final):", msg.content)
|
198 |
-
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
tools = [
|
2 |
{
|
3 |
"type": "function",
|
|
|
99 |
},
|
100 |
},
|
101 |
},
|
102 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
agent/core.py
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
from mistralai import Mistral
|
3 |
+
from agent.agent_config import prompts
|
4 |
+
from agent.agent_config import tool_schema
|
5 |
+
from config import MISTRAL_API_KEY
|
6 |
+
from tools.code_index import retrieve_context
|
7 |
+
from tools.github_tools import fetch_github_issue, get_issue_details, post_comment
|
8 |
+
|
9 |
+
tools = tool_schema.tools
|
10 |
+
names_to_functions = {
|
11 |
+
"fetch_github_issue": fetch_github_issue,
|
12 |
+
"get_issue_details": get_issue_details,
|
13 |
+
"retrieve_context": retrieve_context,
|
14 |
+
"post_comment": post_comment,
|
15 |
+
}
|
16 |
+
|
17 |
+
allowed_tools = set(names_to_functions.keys())
|
18 |
+
|
19 |
+
system_message = prompts.system_message
|
20 |
+
user_message = {
|
21 |
+
"role": "user",
|
22 |
+
"content": "Please suggest a fix on this issue https://github.com/aditi-dsi/testing-cryptope/issues/4."
|
23 |
+
}
|
24 |
+
|
25 |
+
messages = [system_message, user_message]
|
26 |
+
|
27 |
+
api_key = MISTRAL_API_KEY
|
28 |
+
model = "devstral-small-latest"
|
29 |
+
client = Mistral(api_key=api_key)
|
30 |
+
|
31 |
+
MAX_STEPS = 5
|
32 |
+
tool_calls = 0
|
33 |
+
|
34 |
+
while True:
|
35 |
+
response = client.chat.complete(
|
36 |
+
model=model,
|
37 |
+
messages=messages,
|
38 |
+
tools=tools,
|
39 |
+
tool_choice="any",
|
40 |
+
)
|
41 |
+
msg = response.choices[0].message
|
42 |
+
messages.append(msg)
|
43 |
+
|
44 |
+
if hasattr(msg, "tool_calls") and msg.tool_calls:
|
45 |
+
for tool_call in msg.tool_calls:
|
46 |
+
function_name = tool_call.function.name
|
47 |
+
function_params = json.loads(tool_call.function.arguments)
|
48 |
+
if function_name in allowed_tools:
|
49 |
+
function_result = names_to_functions[function_name](**function_params)
|
50 |
+
print(f"Agent is calling tool: {function_name}")
|
51 |
+
tool_calls += 1
|
52 |
+
messages.append({
|
53 |
+
"role": "tool",
|
54 |
+
"tool_call_id": tool_call.id,
|
55 |
+
"content": str(function_result)
|
56 |
+
})
|
57 |
+
|
58 |
+
if function_name == "post_comment":
|
59 |
+
print("OpenSorus (final): ✅ Comment posted successfully. No further action needed.")
|
60 |
+
exit(0)
|
61 |
+
|
62 |
+
else:
|
63 |
+
print(f"LLM tried to call unknown tool: {function_name}")
|
64 |
+
tool_error_msg = (
|
65 |
+
f"Error: Tool '{function_name}' is not available. "
|
66 |
+
"You can only use the following tools: fetch_github_issue, get_issue_details, post_comment."
|
67 |
+
)
|
68 |
+
messages.append({
|
69 |
+
"role": "tool",
|
70 |
+
"tool_call_id": tool_call.id,
|
71 |
+
"content": tool_error_msg
|
72 |
+
})
|
73 |
+
if tool_calls >= MAX_STEPS:
|
74 |
+
print(f"Agent stopped after {MAX_STEPS} tool calls to protect against rate limiting.")
|
75 |
+
break
|
76 |
+
else:
|
77 |
+
print("OpenSorus (final):", msg.content)
|
78 |
+
break
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
llama_index==0.12.40
|
2 |
+
mistralai==1.8.1
|
3 |
+
PyJWT==2.10.1
|
4 |
+
python-dotenv==1.1.0
|
5 |
+
requests==2.32.3
|
tools/__init__.py
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
__all__ = [
|
2 |
+
"code_index",
|
3 |
+
"github_tools",
|
4 |
+
]
|
{agent → tools}/code_index.py
RENAMED
@@ -1,4 +1,3 @@
|
|
1 |
-
import base64
|
2 |
import os
|
3 |
import re
|
4 |
import time
|
@@ -9,55 +8,13 @@ from llama_index.core.postprocessor import SimilarityPostprocessor
|
|
9 |
from llama_index.embeddings.mistralai import MistralAIEmbedding
|
10 |
from llama_index.llms.mistralai import MistralAI
|
11 |
from mistralai import Mistral
|
12 |
-
from agent.function_calling import github_request, get_installation_id, get_installation_token
|
13 |
from config import MISTRAL_API_KEY
|
|
|
|
|
14 |
|
15 |
repo_indices_cache: Dict[str, VectorStoreIndex] = {}
|
16 |
INCLUDE_FILE_EXTENSIONS = {".py", ".js", ".ts", ".json", ".md", ".txt"}
|
17 |
|
18 |
-
def fetch_repo_files(owner: str, repo: str, ref: str = "main") -> List[str]:
|
19 |
-
"""
|
20 |
-
Lists all files in the repository by recursively fetching the Git tree from GitHub API.
|
21 |
-
Returns a list of file paths.
|
22 |
-
"""
|
23 |
-
installation_id = get_installation_id(owner, repo)
|
24 |
-
token = get_installation_token(installation_id)
|
25 |
-
url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/{ref}?recursive=1"
|
26 |
-
headers = {
|
27 |
-
"Authorization": f"Bearer {token}",
|
28 |
-
"Accept": "application/vnd.github.v3+json"
|
29 |
-
}
|
30 |
-
response = github_request("GET", url, headers=headers)
|
31 |
-
if response.status_code != 200:
|
32 |
-
raise Exception(f"Failed to list repository files: {response.status_code} {response.text}")
|
33 |
-
|
34 |
-
tree = response.json().get("tree", [])
|
35 |
-
file_paths = [item["path"] for item in tree if item["type"] == "blob"]
|
36 |
-
return file_paths
|
37 |
-
|
38 |
-
# print(fetch_repo_files("aditi-dsi", "EvalAI-Starters", "master"))
|
39 |
-
|
40 |
-
def fetch_file_content(owner: str, repo: str, path: str, ref: str = "main") -> str:
|
41 |
-
"""
|
42 |
-
Fetches the content of a file from the GitHub repository.
|
43 |
-
"""
|
44 |
-
installation_id = get_installation_id(owner, repo)
|
45 |
-
token = get_installation_token(installation_id)
|
46 |
-
url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}?ref={ref}"
|
47 |
-
headers = {
|
48 |
-
"Authorization": f"Bearer {token}",
|
49 |
-
"Accept": "application/vnd.github.v3+json"
|
50 |
-
}
|
51 |
-
response = github_request("GET", url, headers=headers)
|
52 |
-
if response.status_code != 200:
|
53 |
-
raise Exception(f"Failed to fetch file content {path}: {response.status_code} {response.text}")
|
54 |
-
|
55 |
-
content_json = response.json()
|
56 |
-
content = base64.b64decode(content_json["content"]).decode("utf-8", errors="ignore")
|
57 |
-
return content
|
58 |
-
|
59 |
-
# print(fetch_file_content("aditi-dsi", "testing-cryptope", "frontend/src/lib/buildSwap.ts", "main"))
|
60 |
-
|
61 |
def clean_line(line: str) -> str:
|
62 |
line = re.sub(r'^\s*\d+[\.\)]\s*', '', line)
|
63 |
line = line.strip(' `"\'')
|
|
|
|
|
1 |
import os
|
2 |
import re
|
3 |
import time
|
|
|
8 |
from llama_index.embeddings.mistralai import MistralAIEmbedding
|
9 |
from llama_index.llms.mistralai import MistralAI
|
10 |
from mistralai import Mistral
|
|
|
11 |
from config import MISTRAL_API_KEY
|
12 |
+
from tools.utils import fetch_repo_files, fetch_file_content
|
13 |
+
|
14 |
|
15 |
repo_indices_cache: Dict[str, VectorStoreIndex] = {}
|
16 |
INCLUDE_FILE_EXTENSIONS = {".py", ".js", ".ts", ".json", ".md", ".txt"}
|
17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
def clean_line(line: str) -> str:
|
19 |
line = re.sub(r'^\s*\d+[\.\)]\s*', '', line)
|
20 |
line = line.strip(' `"\'')
|
tools/github_tools.py
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from urllib.parse import urlparse
|
2 |
+
from tools.utils import get_installation_id, get_installation_token, github_request
|
3 |
+
|
4 |
+
def fetch_github_issue(issue_url):
|
5 |
+
parsed = urlparse(issue_url)
|
6 |
+
path_parts = parsed.path.strip('/').split('/')
|
7 |
+
if len(path_parts) >= 4 and path_parts[2] == 'issues':
|
8 |
+
owner = path_parts[0]
|
9 |
+
repo = path_parts[1]
|
10 |
+
issue_num = path_parts[3]
|
11 |
+
return owner, repo, issue_num
|
12 |
+
else:
|
13 |
+
raise ValueError("Invalid GitHub Issue URL")
|
14 |
+
|
15 |
+
|
16 |
+
def get_issue_details(owner, repo, issue_num):
|
17 |
+
installation_id = get_installation_id(owner, repo)
|
18 |
+
token = get_installation_token(installation_id)
|
19 |
+
url = f"https://api.github.com/repos/{owner}/{repo}/issues/{issue_num}"
|
20 |
+
headers = {
|
21 |
+
"Authorization": f"Bearer {token}",
|
22 |
+
"Accept": "application/vnd.github.v3+json"
|
23 |
+
}
|
24 |
+
response = github_request("GET", url, headers=headers)
|
25 |
+
if response.status_code == 200:
|
26 |
+
return response.json()
|
27 |
+
else:
|
28 |
+
raise Exception(f"Failed to fetch issue: {response.status_code} {response.text}")
|
29 |
+
|
30 |
+
# print(get_issue_details("aditi-dsi", "testing-cryptope", "3"))
|
31 |
+
|
32 |
+
def post_comment(owner, repo, issue_num, comment_body):
|
33 |
+
installation_id = get_installation_id(owner, repo)
|
34 |
+
token = get_installation_token(installation_id)
|
35 |
+
url = f"https://api.github.com/repos/{owner}/{repo}/issues/{issue_num}/comments"
|
36 |
+
headers = {
|
37 |
+
"Authorization": f"Bearer {token}",
|
38 |
+
"Accept": "application/vnd.github.v3+json"
|
39 |
+
}
|
40 |
+
data = {"body": comment_body}
|
41 |
+
response = github_request("POST", url, headers=headers, json=data)
|
42 |
+
if response.status_code == 201:
|
43 |
+
return response.json()
|
44 |
+
else:
|
45 |
+
raise Exception(f"Failed to post comment: {response.status_code} {response.text}")
|
46 |
+
|
47 |
+
# print(post_comment("aditi-dsi", "testing-cryptope", "3", "This is a test comment from OpenSorus."))
|
agent/function_calling.py → tools/utils.py
RENAMED
@@ -1,14 +1,17 @@
|
|
1 |
-
import
|
2 |
-
from urllib.parse import urlparse
|
3 |
-
from config import APP_ID, APP_PRIVATE_KEY
|
4 |
-
import time
|
5 |
-
import jwt
|
6 |
from datetime import datetime, timezone, timedelta
|
|
|
7 |
import threading
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
installation_tokens = {}
|
10 |
token_lock = threading.Lock()
|
11 |
|
|
|
12 |
def generate_jwt():
|
13 |
"""Generate a JWT signed with GitHub App private key."""
|
14 |
now = int(time.time())
|
@@ -90,121 +93,46 @@ def get_installation_token(installation_id):
|
|
90 |
|
91 |
# print(get_installation_token(69452220))
|
92 |
|
93 |
-
def
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
repo = path_parts[1]
|
99 |
-
issue_num = path_parts[3]
|
100 |
-
return owner, repo, issue_num
|
101 |
-
else:
|
102 |
-
raise ValueError("Invalid GitHub Issue URL")
|
103 |
-
|
104 |
-
|
105 |
-
def get_issue_details(owner, repo, issue_num):
|
106 |
installation_id = get_installation_id(owner, repo)
|
107 |
token = get_installation_token(installation_id)
|
108 |
-
url = f"https://api.github.com/repos/{owner}/{repo}/
|
109 |
headers = {
|
110 |
"Authorization": f"Bearer {token}",
|
111 |
"Accept": "application/vnd.github.v3+json"
|
112 |
}
|
113 |
response = github_request("GET", url, headers=headers)
|
114 |
-
if response.status_code
|
115 |
-
|
116 |
-
|
117 |
-
|
|
|
|
|
118 |
|
119 |
-
# print(
|
120 |
|
121 |
-
def
|
|
|
|
|
|
|
122 |
installation_id = get_installation_id(owner, repo)
|
123 |
token = get_installation_token(installation_id)
|
124 |
-
url = f"https://api.github.com/repos/{owner}/{repo}/
|
125 |
headers = {
|
126 |
"Authorization": f"Bearer {token}",
|
127 |
"Accept": "application/vnd.github.v3+json"
|
128 |
}
|
129 |
-
|
130 |
-
response
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
# {
|
140 |
-
# "type": "function",
|
141 |
-
# "function": {
|
142 |
-
# "name": "fetch_github_issue",
|
143 |
-
# "description": "Fetch GitHub issue details",
|
144 |
-
# "parameters": {
|
145 |
-
# "type": "object",
|
146 |
-
# "properties": {
|
147 |
-
# "issue_url": {
|
148 |
-
# "type": "string",
|
149 |
-
# "description": "The full URL of the GitHub issue"
|
150 |
-
# }
|
151 |
-
# },
|
152 |
-
# "required": ["issue_url"]
|
153 |
-
# },
|
154 |
-
# },
|
155 |
-
# },
|
156 |
-
# {
|
157 |
-
# "type": "function",
|
158 |
-
# "function": {
|
159 |
-
# "name": "get_issue_details",
|
160 |
-
# "description": "Get details of a GitHub issue",
|
161 |
-
# "parameters": {
|
162 |
-
# "type": "object",
|
163 |
-
# "properties": {
|
164 |
-
# "owner": {
|
165 |
-
# "type": "string",
|
166 |
-
# "description": "The owner of the repository."
|
167 |
-
# },
|
168 |
-
# "repo": {
|
169 |
-
# "type": "string",
|
170 |
-
# "description": "The name of the repository."
|
171 |
-
# },
|
172 |
-
# "issue_num": {
|
173 |
-
# "type": "string",
|
174 |
-
# "description": "The issue number."
|
175 |
-
# }
|
176 |
-
# },
|
177 |
-
# "required": ["owner", "repo", "issue_num"],
|
178 |
-
# },
|
179 |
-
# },
|
180 |
-
# },
|
181 |
-
# {
|
182 |
-
# "type": "function",
|
183 |
-
# "function": {
|
184 |
-
# "name": "post_comment",
|
185 |
-
# "description": "Post a comment on a GitHub issue",
|
186 |
-
# "parameters": {
|
187 |
-
# "type": "object",
|
188 |
-
# "properties": {
|
189 |
-
# "owner": {
|
190 |
-
# "type": "string",
|
191 |
-
# "description": "The owner of the repository."
|
192 |
-
# },
|
193 |
-
# "repo": {
|
194 |
-
# "type": "string",
|
195 |
-
# "description": "The name of the repository."
|
196 |
-
# },
|
197 |
-
# "issue_num": {
|
198 |
-
# "type": "string",
|
199 |
-
# "description": "The issue number."
|
200 |
-
# },
|
201 |
-
# "comment_body": {
|
202 |
-
# "type": "string",
|
203 |
-
# "description": "The body of the comment."
|
204 |
-
# }
|
205 |
-
# },
|
206 |
-
# "required": ["owner", "repo", "issue_num", "comment_body"],
|
207 |
-
# },
|
208 |
-
# },
|
209 |
-
# },
|
210 |
-
# ]
|
|
|
1 |
+
import base64
|
|
|
|
|
|
|
|
|
2 |
from datetime import datetime, timezone, timedelta
|
3 |
+
import jwt
|
4 |
import threading
|
5 |
+
import time
|
6 |
+
from typing import List
|
7 |
+
import requests
|
8 |
+
from config import APP_ID, APP_PRIVATE_KEY
|
9 |
+
|
10 |
|
11 |
installation_tokens = {}
|
12 |
token_lock = threading.Lock()
|
13 |
|
14 |
+
|
15 |
def generate_jwt():
|
16 |
"""Generate a JWT signed with GitHub App private key."""
|
17 |
now = int(time.time())
|
|
|
93 |
|
94 |
# print(get_installation_token(69452220))
|
95 |
|
96 |
+
def fetch_repo_files(owner: str, repo: str, ref: str = "main") -> List[str]:
|
97 |
+
"""
|
98 |
+
Lists all files in the repository by recursively fetching the Git tree from GitHub API.
|
99 |
+
Returns a list of file paths.
|
100 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
installation_id = get_installation_id(owner, repo)
|
102 |
token = get_installation_token(installation_id)
|
103 |
+
url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/{ref}?recursive=1"
|
104 |
headers = {
|
105 |
"Authorization": f"Bearer {token}",
|
106 |
"Accept": "application/vnd.github.v3+json"
|
107 |
}
|
108 |
response = github_request("GET", url, headers=headers)
|
109 |
+
if response.status_code != 200:
|
110 |
+
raise Exception(f"Failed to list repository files: {response.status_code} {response.text}")
|
111 |
+
|
112 |
+
tree = response.json().get("tree", [])
|
113 |
+
file_paths = [item["path"] for item in tree if item["type"] == "blob"]
|
114 |
+
return file_paths
|
115 |
|
116 |
+
# print(fetch_repo_files("aditi-dsi", "EvalAI-Starters", "master"))
|
117 |
|
118 |
+
def fetch_file_content(owner: str, repo: str, path: str, ref: str = "main") -> str:
|
119 |
+
"""
|
120 |
+
Fetches the content of a file from the GitHub repository.
|
121 |
+
"""
|
122 |
installation_id = get_installation_id(owner, repo)
|
123 |
token = get_installation_token(installation_id)
|
124 |
+
url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}?ref={ref}"
|
125 |
headers = {
|
126 |
"Authorization": f"Bearer {token}",
|
127 |
"Accept": "application/vnd.github.v3+json"
|
128 |
}
|
129 |
+
response = github_request("GET", url, headers=headers)
|
130 |
+
if response.status_code != 200:
|
131 |
+
raise Exception(f"Failed to fetch file content {path}: {response.status_code} {response.text}")
|
132 |
+
|
133 |
+
content_json = response.json()
|
134 |
+
content = base64.b64decode(content_json["content"]).decode("utf-8", errors="ignore")
|
135 |
+
return content
|
136 |
+
|
137 |
+
# print(fetch_file_content("aditi-dsi", "testing-cryptope", "frontend/src/lib/buildSwap.ts", "main"))
|
138 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|