diff --git a/config.py b/config.py index df208467..459919ff 100644 --- a/config.py +++ b/config.py @@ -74,7 +74,7 @@ def get_env_var(name: str) -> str: IS_PRD: bool = ENV == "prod" # Update here too: https://dashboard.stripe.com/test/products/prod_PokLGIxiVUwCi6 FREE_TIER_REQUEST_AMOUNT = 5 -ISSUE_NUMBER_FORMAT = "/issue-#" +ISSUE_NUMBER_FORMAT = "/issue-" # DO NOT USE "#" as it is a special character and has to be encoded in URL, like in GitHub API URL MAX_RETRIES = 3 PER_PAGE = 100 PR_BODY_STARTS_WITH = "Resolves #" # https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue diff --git a/main.py b/main.py index d873f4c1..9c301060 100644 --- a/main.py +++ b/main.py @@ -44,7 +44,7 @@ async def handle_webhook(request: Request) -> dict[str, str]: ) event_name: str = request.headers.get("X-GitHub-Event", "Event not specified") print("\n" * 3 + "-" * 70) - print(f"Received event: {event_name} with content type: {content_type}\n") + print(f"Received event: {event_name} with content type: {content_type}") # Validate if the webhook signature comes from GitHub await verify_webhook_signature(request=request, secret=GITHUB_WEBHOOK_SECRET) diff --git a/services/github/github_manager.py b/services/github/github_manager.py index 0534866d..d57b41bd 100644 --- a/services/github/github_manager.py +++ b/services/github/github_manager.py @@ -214,7 +214,6 @@ def commit_changes_to_remote_branch( @handle_exceptions(raise_on_error=True) def create_comment(issue_number: int, body: str, base_args: BaseArgs) -> str: """https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#create-an-issue-comment""" - print(body + "\n") owner, repo, token = base_args["owner"], base_args["repo"], base_args["token"] response: requests.Response = requests.post( url=f"{GITHUB_API_URL}/repos/{owner}/{repo}/issues/{issue_number}/comments", @@ -298,10 +297,10 @@ def create_comment_on_issue_with_gitauto_button(payload: GitHubLabeledPayload) - return response.json() -def create_headers(token: str, media_type: Optional[str] = "v3") -> dict[str, str]: +def create_headers(token: str, media_type: Optional[str] = ".v3") -> dict[str, str]: """https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api?apiVersion=2022-11-28#headers""" return { - "Accept": f"application/vnd.github.{media_type}+json", + "Accept": f"application/vnd.github{media_type}+json", "Authorization": f"Bearer {token}", "User-Agent": GITHUB_APP_NAME, "X-GitHub-Api-Version": GITHUB_API_VERSION, @@ -344,7 +343,8 @@ def create_pull_request(body: str, title: str, base_args: BaseArgs) -> str | Non response.raise_for_status() pr_data = response.json() pr_number = pr_data["number"] - """https://docs.github.com/en/rest/pulls/review-requests?apiVersion=2022-11-28#request-reviewers-for-a-pull-request""" + + # https://docs.github.com/en/rest/pulls/review-requests?apiVersion=2022-11-28#request-reviewers-for-a-pull-request response: requests.Response = requests.post( url=f"{GITHUB_API_URL}/repos/{owner}/{repo}/pulls/{pr_number}/requested_reviewers", headers=create_headers(token=token), @@ -454,8 +454,6 @@ def get_issue_comments(issue_number: int, base_args: BaseArgs) -> list[str]: or comment["performed_via_github_app"].get("id") not in GITHUB_APP_IDS ] comment_texts: list[str] = [comment["body"] for comment in filtered_comments] - if comment_texts: - print(f"\nIssue comments: {json.dumps(comment_texts, indent=2)}\n") return comment_texts @@ -634,7 +632,8 @@ def get_remote_file_content( numbered_content: str = "\n".join(numbered_lines) msg = f"Opened file: '{file_path}' with line numbers for your information.\n\n" - return msg + f"```{file_path_with_lines}\n{numbered_content}\n```" + output = msg + f"```{file_path_with_lines}\n{numbered_content}\n```" + return output @handle_exceptions(default_return_value="", raise_on_error=False) @@ -679,7 +678,6 @@ def get_remote_file_tree(base_args: BaseArgs) -> list[str]: https://docs.github.com/en/rest/git/trees?apiVersion=2022-11-28#get-a-tree """ owner, repo, ref = base_args["owner"], base_args["repo"], base_args["base_branch"] - response: requests.Response | None = None # Otherwise response could be Unbound response = requests.get( url=f"{GITHUB_API_URL}/repos/{owner}/{repo}/git/trees/{ref}", headers=create_headers(token=base_args["token"]), diff --git a/services/github/github_types.py b/services/github/github_types.py index f79b78ba..7903559a 100644 --- a/services/github/github_types.py +++ b/services/github/github_types.py @@ -1,4 +1,6 @@ +from dataclasses import dataclass from typing import TypedDict, Dict, List, Optional, Union +import datetime class BaseArgs(TypedDict): @@ -12,6 +14,186 @@ class BaseArgs(TypedDict): token: str +@dataclass +class Owner: + login: str + id: int + node_id: str + avatar_url: str + gravatar_id: str + url: str + html_url: str + followers_url: str + following_url: str + gists_url: str + starred_url: str + subscriptions_url: str + organizations_url: str + repos_url: str + events_url: str + received_events_url: str + type: str + user_view_type: str + site_admin: bool + + +@dataclass +class App: + id: int + client_id: str + slug: str + node_id: str + owner: Owner + name: str + description: str + external_url: str + html_url: str + created_at: datetime + updated_at: datetime + permissions: dict + events: List[str] + + +@dataclass +class PullRequest: + url: str + id: int + number: int + head: dict + base: dict + + +@dataclass +class CheckSuite: + id: int + node_id: str + head_branch: str + head_sha: str + status: str + conclusion: Optional[str] + url: str + before: str + after: str + pull_requests: List[PullRequest] + app: App + created_at: datetime + updated_at: datetime + + +@dataclass +class Output: + title: Optional[str] + summary: Optional[str] + text: Optional[str] + annotations_count: int + annotations_url: str + + +@dataclass +class CheckRun: + id: int + name: str + node_id: str + head_sha: str + external_id: str + url: str + html_url: str + details_url: str + status: str + conclusion: str + started_at: datetime + completed_at: datetime + output: Output + check_suite: CheckSuite + app: App + pull_requests: List[PullRequest] + + +@dataclass +class Repository: + id: int + node_id: str + name: str + full_name: str + private: bool + owner: Owner + html_url: str + description: str + fork: bool + url: str + created_at: datetime + updated_at: datetime + pushed_at: datetime + git_url: str + ssh_url: str + clone_url: str + svn_url: str + homepage: str + size: int + stargazers_count: int + watchers_count: int + language: Optional[str] + has_issues: bool + has_projects: bool + has_downloads: bool + has_wiki: bool + has_pages: bool + has_discussions: bool + forks_count: int + mirror_url: Optional[str] + archived: bool + disabled: bool + open_issues_count: int + license: dict + allow_forking: bool + is_template: bool + web_commit_signoff_required: bool + topics: List[str] + visibility: str + forks: int + open_issues: int + watchers: int + default_branch: str + + +@dataclass +class Sender: + login: str + id: int + node_id: str + avatar_url: str + gravatar_id: str + url: str + html_url: str + followers_url: str + following_url: str + gists_url: str + starred_url: str + subscriptions_url: str + organizations_url: str + repos_url: str + events_url: str + received_events_url: str + type: str + user_view_type: str + site_admin: bool + + +@dataclass +class Installation: + id: int + node_id: str + + +@dataclass +class CheckRunCompletedPayload: + action: str + check_run: CheckRun + repository: Repository + sender: Sender + installation: Installation + + class LabelInfo(TypedDict): id: int node_id: str @@ -228,7 +410,8 @@ class GitHubLabeledPayload(TypedDict): class GitHubContentInfo(TypedDict): - """ https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28 """ + """https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28""" + type: str encoding: str size: int diff --git a/services/openai/commit_changes.py b/services/openai/commit_changes.py index 2f10b072..0e01bc8c 100644 --- a/services/openai/commit_changes.py +++ b/services/openai/commit_changes.py @@ -73,7 +73,7 @@ def explore_repo_or_commit_changes( # Return if no tool calls is_done = False if not tool_calls: - print("No tool calls: ", choice.message.content) + print(f"No tool called in '{mode}' mode") return messages, previous_calls, token_input, token_output, is_done # Handle multiple tool calls diff --git a/services/webhook_handler.py b/services/webhook_handler.py index 0458bd73..e7da43b9 100644 --- a/services/webhook_handler.py +++ b/services/webhook_handler.py @@ -1,5 +1,6 @@ # Standard imports import re +from typing import Any # Local imports from config import ( @@ -15,10 +16,7 @@ get_installation_access_token, # turn_on_issue, ) -from services.github.github_types import ( - GitHubEventPayload, - GitHubInstallationPayload, -) +from services.github.github_types import GitHubInstallationPayload from services.supabase import SupabaseManager from services.gitauto_handler import handle_gitauto from utils.handle_exceptions import handle_exceptions @@ -79,7 +77,7 @@ async def handle_installation_repos_added(payload) -> None: @handle_exceptions(default_return_value=None, raise_on_error=True) -async def handle_webhook_event(event_name: str, payload: GitHubEventPayload) -> None: +async def handle_webhook_event(event_name: str, payload: dict[str, Any]) -> None: """ Determine the event type and call the appropriate handler. Check the type of webhook event and handle accordingly. @@ -167,4 +165,4 @@ async def handle_webhook_event(event_name: str, payload: GitHubEventPayload) -> return # Unhandled events are captured here - print(f"Event {event_name} with action {action} is not handled") + print(f"Event '{event_name}' with action '{action}' was received but skipped.")