T7yrttrd commited on
Commit
46b8c29
·
verified ·
1 Parent(s): 4451a00

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +508 -11
app.py CHANGED
@@ -66,7 +66,7 @@ OAUTH_CONFIGS = {
66
  "client_secret": "ec8548ef297ed59d8372e5bc549d8ed9adc996f7",
67
  "auth_url": "https://github.com/login/oauth/authorize",
68
  "token_url": "https://github.com/login/oauth/access_token",
69
- "scope": "read:user repo"
70
  },
71
  "figma": {
72
  "client_id": "Cg79RWBHaeyW2afGJVSQi8",
@@ -137,11 +137,12 @@ STRICT IDENTITY RULES:
137
  - To Delete/Remove a page: Search for the page_id, then call `notion_delete_page`. NEVER tell the user to delete manually.
138
  - To Create a new page: Use `notion_create_page`.
139
 
140
- 4. GITHUB CONNECTOR WORKFLOW RULES:
141
- - To view user repositories, use `github_list_repos`.
142
- - To read repository content or file text, use `github_get_repo_content`.
143
- - To check user details or profile, use `github_get_user_profile`.
144
- - To view issues, use `github_list_issues`. To create issue, use `github_create_issue`.
 
145
 
146
  5. NATURAL CONVERSATION & TOOL RESPONSE STYLE:
147
  - Do NOT introduce your company, founder, or model name in general responses unless requested.
@@ -495,17 +496,173 @@ TOOLS = [
495
  "properties": {
496
  "owner": {"type": "string", "description": "Repository owner / username"},
497
  "repo": {"type": "string", "description": "Repository name"},
498
- "path": {"type": "string", "description": "Path to file or folder, default empty for root"}
 
499
  },
500
  "required": ["owner", "repo"]
501
  }
502
  }
503
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
504
  {
505
  "type": "function",
506
  "function": {
507
  "name": "github_list_issues",
508
- "description": "List issues or pull requests in a GitHub repository",
509
  "parameters": {
510
  "type": "object",
511
  "properties": {
@@ -533,6 +690,71 @@ TOOLS = [
533
  "required": ["owner", "repo", "title"]
534
  }
535
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536
  }
537
  ]
538
 
@@ -665,12 +887,14 @@ async def github_list_repos(user_id: str, max_results: int = 10):
665
  except Exception as e:
666
  return f"Error listing GitHub repos: {str(e)}"
667
 
668
- async def github_get_repo_content(user_id: str, owner: str, repo: str, path: str = ""):
669
  headers = await github_get_headers(user_id)
670
  if not headers:
671
  return "GitHub is not connected."
672
  try:
673
  url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}"
 
 
674
  async with httpx.AsyncClient() as c:
675
  res = await c.get(url, headers=headers)
676
  if res.status_code != 200:
@@ -685,11 +909,185 @@ async def github_get_repo_content(user_id: str, owner: str, repo: str, path: str
685
  if data.get("encoding") == "base64" and data.get("content"):
686
  content = base64.b64decode(data.get("content")).decode("utf-8", errors="ignore")
687
  return content
688
- return f"File Name: {data.get('name')} | Size: {data.get('size')} bytes"
689
  return "Unable to parse content."
690
  except Exception as e:
691
  return f"Error reading GitHub content: {str(e)}"
692
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
693
  async def github_list_issues(user_id: str, owner: str, repo: str, state: str = "open"):
694
  headers = await github_get_headers(user_id)
695
  if not headers:
@@ -726,6 +1124,79 @@ async def github_create_issue(user_id: str, owner: str, repo: str, title: str, b
726
  except Exception as e:
727
  return f"Error creating GitHub issue: {str(e)}"
728
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
729
  async def notion_get_headers(user_id: str):
730
  token_data = await get_token_from_supabase(user_id, "notion")
731
  if not token_data:
@@ -1231,11 +1702,37 @@ async def execute_tool_call(user_id: str, func_name: str, args: dict) -> str:
1231
  elif func_name == "github_list_repos":
1232
  return await github_list_repos(user_id, args.get("max_results", 10))
1233
  elif func_name == "github_get_repo_content":
1234
- return await github_get_repo_content(user_id, args.get("owner", ""), args.get("repo", ""), args.get("path", ""))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1235
  elif func_name == "github_list_issues":
1236
  return await github_list_issues(user_id, args.get("owner", ""), args.get("repo", ""), args.get("state", "open"))
1237
  elif func_name == "github_create_issue":
1238
  return await github_create_issue(user_id, args.get("owner", ""), args.get("repo", ""), args.get("title", ""), args.get("body", ""))
 
 
 
 
 
 
 
 
1239
  return "Unknown tool function."
1240
 
1241
  def count_tokens(messages):
 
66
  "client_secret": "ec8548ef297ed59d8372e5bc549d8ed9adc996f7",
67
  "auth_url": "https://github.com/login/oauth/authorize",
68
  "token_url": "https://github.com/login/oauth/access_token",
69
+ "scope": "read:user repo workflow"
70
  },
71
  "figma": {
72
  "client_id": "Cg79RWBHaeyW2afGJVSQi8",
 
137
  - To Delete/Remove a page: Search for the page_id, then call `notion_delete_page`. NEVER tell the user to delete manually.
138
  - To Create a new page: Use `notion_create_page`.
139
 
140
+ 4. GITHUB CONNECTOR WORKFLOW RULES (FULL CAPABILITY):
141
+ - READ & SEARCH: Use `github_get_repo_content` to read files/folders, `github_search_code` to search code, `github_list_commits` for history.
142
+ - EDIT & WRITE: Use `github_create_or_update_file` to create or edit files, `github_delete_file` to remove files.
143
+ - BRANCHES & PRS: Use `github_create_branch`, `github_delete_branch`, `github_list_pull_requests`, `github_get_pull_request`, `github_create_pull_request`, `github_merge_pull_request`.
144
+ - ISSUES & COMMENTS: Use `github_list_issues`, `github_create_issue`, `github_update_issue` (to edit/close/reopen), `github_create_comment`.
145
+ - AUTOMATION: Use `github_list_workflow_runs` to check CI/CD build status.
146
 
147
  5. NATURAL CONVERSATION & TOOL RESPONSE STYLE:
148
  - Do NOT introduce your company, founder, or model name in general responses unless requested.
 
496
  "properties": {
497
  "owner": {"type": "string", "description": "Repository owner / username"},
498
  "repo": {"type": "string", "description": "Repository name"},
499
+ "path": {"type": "string", "description": "Path to file or folder, default empty for root"},
500
+ "branch": {"type": "string", "description": "Branch name, default main/master"}
501
  },
502
  "required": ["owner", "repo"]
503
  }
504
  }
505
  },
506
+ {
507
+ "type": "function",
508
+ "function": {
509
+ "name": "github_create_or_update_file",
510
+ "description": "Create a new file or edit/update an existing file in GitHub repository",
511
+ "parameters": {
512
+ "type": "object",
513
+ "properties": {
514
+ "owner": {"type": "string", "description": "Repository owner"},
515
+ "repo": {"type": "string", "description": "Repository name"},
516
+ "path": {"type": "string", "description": "File path in repository e.g. 'src/App.kt'"},
517
+ "content": {"type": "string", "description": "Text content for the file"},
518
+ "message": {"type": "string", "description": "Commit message"},
519
+ "branch": {"type": "string", "description": "Target branch name"}
520
+ },
521
+ "required": ["owner", "repo", "path", "content", "message"]
522
+ }
523
+ }
524
+ },
525
+ {
526
+ "type": "function",
527
+ "function": {
528
+ "name": "github_delete_file",
529
+ "description": "Delete a file from GitHub repository",
530
+ "parameters": {
531
+ "type": "object",
532
+ "properties": {
533
+ "owner": {"type": "string", "description": "Repository owner"},
534
+ "repo": {"type": "string", "description": "Repository name"},
535
+ "path": {"type": "string", "description": "File path to delete"},
536
+ "message": {"type": "string", "description": "Commit message"},
537
+ "branch": {"type": "string", "description": "Branch name"}
538
+ },
539
+ "required": ["owner", "repo", "path", "message"]
540
+ }
541
+ }
542
+ },
543
+ {
544
+ "type": "function",
545
+ "function": {
546
+ "name": "github_create_branch",
547
+ "description": "Create a new Git branch in GitHub repository",
548
+ "parameters": {
549
+ "type": "object",
550
+ "properties": {
551
+ "owner": {"type": "string", "description": "Repository owner"},
552
+ "repo": {"type": "string", "description": "Repository name"},
553
+ "new_branch": {"type": "string", "description": "Name of new branch"},
554
+ "base_branch": {"type": "string", "description": "Source branch to copy from, default 'main'"}
555
+ },
556
+ "required": ["owner", "repo", "new_branch"]
557
+ }
558
+ }
559
+ },
560
+ {
561
+ "type": "function",
562
+ "function": {
563
+ "name": "github_delete_branch",
564
+ "description": "Delete a branch from GitHub repository",
565
+ "parameters": {
566
+ "type": "object",
567
+ "properties": {
568
+ "owner": {"type": "string", "description": "Repository owner"},
569
+ "repo": {"type": "string", "description": "Repository name"},
570
+ "branch": {"type": "string", "description": "Branch name to delete"}
571
+ },
572
+ "required": ["owner", "repo", "branch"]
573
+ }
574
+ }
575
+ },
576
+ {
577
+ "type": "function",
578
+ "function": {
579
+ "name": "github_list_commits",
580
+ "description": "List commit history for a GitHub repository or specific file path",
581
+ "parameters": {
582
+ "type": "object",
583
+ "properties": {
584
+ "owner": {"type": "string", "description": "Repository owner"},
585
+ "repo": {"type": "string", "description": "Repository name"},
586
+ "path": {"type": "string", "description": "Optional file path filter"},
587
+ "max_results": {"type": "integer", "description": "Number of commits, default 10"}
588
+ },
589
+ "required": ["owner", "repo"]
590
+ }
591
+ }
592
+ },
593
+ {
594
+ "type": "function",
595
+ "function": {
596
+ "name": "github_list_pull_requests",
597
+ "description": "List Pull Requests (PRs) in a GitHub repository",
598
+ "parameters": {
599
+ "type": "object",
600
+ "properties": {
601
+ "owner": {"type": "string", "description": "Repository owner"},
602
+ "repo": {"type": "string", "description": "Repository name"},
603
+ "state": {"type": "string", "description": "State e.g. 'open', 'closed', 'all'"}
604
+ },
605
+ "required": ["owner", "repo"]
606
+ }
607
+ }
608
+ },
609
+ {
610
+ "type": "function",
611
+ "function": {
612
+ "name": "github_get_pull_request",
613
+ "description": "Get detailed information and diff summary of a Pull Request",
614
+ "parameters": {
615
+ "type": "object",
616
+ "properties": {
617
+ "owner": {"type": "string", "description": "Repository owner"},
618
+ "repo": {"type": "string", "description": "Repository name"},
619
+ "pull_number": {"type": "integer", "description": "PR number"}
620
+ },
621
+ "required": ["owner", "repo", "pull_number"]
622
+ }
623
+ }
624
+ },
625
+ {
626
+ "type": "function",
627
+ "function": {
628
+ "name": "github_create_pull_request",
629
+ "description": "Create a new Pull Request in GitHub repository",
630
+ "parameters": {
631
+ "type": "object",
632
+ "properties": {
633
+ "owner": {"type": "string", "description": "Repository owner"},
634
+ "repo": {"type": "string", "description": "Repository name"},
635
+ "title": {"type": "string", "description": "PR title"},
636
+ "head": {"type": "string", "description": "Branch containing new code"},
637
+ "base": {"type": "string", "description": "Target branch to merge into e.g. 'main'"},
638
+ "body": {"type": "string", "description": "PR description"}
639
+ },
640
+ "required": ["owner", "repo", "title", "head", "base"]
641
+ }
642
+ }
643
+ },
644
+ {
645
+ "type": "function",
646
+ "function": {
647
+ "name": "github_merge_pull_request",
648
+ "description": "Merge a Pull Request in GitHub repository",
649
+ "parameters": {
650
+ "type": "object",
651
+ "properties": {
652
+ "owner": {"type": "string", "description": "Repository owner"},
653
+ "repo": {"type": "string", "description": "Repository name"},
654
+ "pull_number": {"type": "integer", "description": "PR number"},
655
+ "commit_title": {"type": "string", "description": "Optional merge commit message"}
656
+ },
657
+ "required": ["owner", "repo", "pull_number"]
658
+ }
659
+ }
660
+ },
661
  {
662
  "type": "function",
663
  "function": {
664
  "name": "github_list_issues",
665
+ "description": "List issues in a GitHub repository",
666
  "parameters": {
667
  "type": "object",
668
  "properties": {
 
690
  "required": ["owner", "repo", "title"]
691
  }
692
  }
693
+ },
694
+ {
695
+ "type": "function",
696
+ "function": {
697
+ "name": "github_update_issue",
698
+ "description": "Update or close/reopen an existing issue or Pull Request in GitHub",
699
+ "parameters": {
700
+ "type": "object",
701
+ "properties": {
702
+ "owner": {"type": "string", "description": "Repository owner"},
703
+ "repo": {"type": "string", "description": "Repository name"},
704
+ "issue_number": {"type": "integer", "description": "Issue number"},
705
+ "title": {"type": "string", "description": "New title"},
706
+ "body": {"type": "string", "description": "New body content"},
707
+ "state": {"type": "string", "description": "State 'open' or 'closed'"}
708
+ },
709
+ "required": ["owner", "repo", "issue_number"]
710
+ }
711
+ }
712
+ },
713
+ {
714
+ "type": "function",
715
+ "function": {
716
+ "name": "github_create_comment",
717
+ "description": "Add a comment to an issue or Pull Request in GitHub",
718
+ "parameters": {
719
+ "type": "object",
720
+ "properties": {
721
+ "owner": {"type": "string", "description": "Repository owner"},
722
+ "repo": {"type": "string", "description": "Repository name"},
723
+ "issue_number": {"type": "integer", "description": "Issue or PR number"},
724
+ "body": {"type": "string", "description": "Comment text"}
725
+ },
726
+ "required": ["owner", "repo", "issue_number", "body"]
727
+ }
728
+ }
729
+ },
730
+ {
731
+ "type": "function",
732
+ "function": {
733
+ "name": "github_search_code",
734
+ "description": "Search code across user repositories on GitHub",
735
+ "parameters": {
736
+ "type": "object",
737
+ "properties": {
738
+ "query": {"type": "string", "description": "Search text or function name e.g. 'function_name user:owner'"}
739
+ },
740
+ "required": ["query"]
741
+ }
742
+ }
743
+ },
744
+ {
745
+ "type": "function",
746
+ "function": {
747
+ "name": "github_list_workflow_runs",
748
+ "description": "List GitHub Actions CI/CD workflow runs and build status",
749
+ "parameters": {
750
+ "type": "object",
751
+ "properties": {
752
+ "owner": {"type": "string", "description": "Repository owner"},
753
+ "repo": {"type": "string", "description": "Repository name"}
754
+ },
755
+ "required": ["owner", "repo"]
756
+ }
757
+ }
758
  }
759
  ]
760
 
 
887
  except Exception as e:
888
  return f"Error listing GitHub repos: {str(e)}"
889
 
890
+ async def github_get_repo_content(user_id: str, owner: str, repo: str, path: str = "", branch: str = None):
891
  headers = await github_get_headers(user_id)
892
  if not headers:
893
  return "GitHub is not connected."
894
  try:
895
  url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}"
896
+ if branch:
897
+ url += f"?ref={branch}"
898
  async with httpx.AsyncClient() as c:
899
  res = await c.get(url, headers=headers)
900
  if res.status_code != 200:
 
909
  if data.get("encoding") == "base64" and data.get("content"):
910
  content = base64.b64decode(data.get("content")).decode("utf-8", errors="ignore")
911
  return content
912
+ return f"File Name: {data.get('name')} | Size: {data.get('size')} bytes | SHA: {data.get('sha')}"
913
  return "Unable to parse content."
914
  except Exception as e:
915
  return f"Error reading GitHub content: {str(e)}"
916
 
917
+ async def github_create_or_update_file(user_id: str, owner: str, repo: str, path: str, content: str, message: str, branch: str = None):
918
+ headers = await github_get_headers(user_id)
919
+ if not headers:
920
+ return "GitHub is not connected."
921
+ try:
922
+ url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}"
923
+ encoded_content = base64.b64encode(content.encode("utf-8")).decode("utf-8")
924
+ payload = {
925
+ "message": message,
926
+ "content": encoded_content
927
+ }
928
+ if branch:
929
+ payload["branch"] = branch
930
+ async with httpx.AsyncClient() as c:
931
+ get_res = await c.get(url + (f"?ref={branch}" if branch else ""), headers=headers)
932
+ if get_res.status_code == 200:
933
+ file_info = get_res.json()
934
+ if isinstance(file_info, dict) and "sha" in file_info:
935
+ payload["sha"] = file_info["sha"]
936
+ res = await c.put(url, headers=headers, json=payload)
937
+ if res.status_code not in [200, 201]:
938
+ return f"Error updating file: {res.text}"
939
+ data = res.json()
940
+ return f"Successfully saved file '{path}' in commit: {data.get('commit', {}).get('sha')}"
941
+ except Exception as e:
942
+ return f"Error modifying file: {str(e)}"
943
+
944
+ async def github_delete_file(user_id: str, owner: str, repo: str, path: str, message: str, branch: str = None):
945
+ headers = await github_get_headers(user_id)
946
+ if not headers:
947
+ return "GitHub is not connected."
948
+ try:
949
+ url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}"
950
+ async with httpx.AsyncClient() as c:
951
+ get_res = await c.get(url + (f"?ref={branch}" if branch else ""), headers=headers)
952
+ if get_res.status_code != 200:
953
+ return f"File '{path}' not found."
954
+ file_info = get_res.json()
955
+ sha = file_info.get("sha")
956
+ payload = {"message": message, "sha": sha}
957
+ if branch: payload["branch"] = branch
958
+ del_res = await c.request("DELETE", url, headers=headers, json=payload)
959
+ if del_res.status_code != 200:
960
+ return f"Error deleting file: {del_res.text}"
961
+ return f"Successfully deleted file '{path}'."
962
+ except Exception as e:
963
+ return f"Error deleting file: {str(e)}"
964
+
965
+ async def github_create_branch(user_id: str, owner: str, repo: str, new_branch: str, base_branch: str = "main"):
966
+ headers = await github_get_headers(user_id)
967
+ if not headers:
968
+ return "GitHub is not connected."
969
+ try:
970
+ async with httpx.AsyncClient() as c:
971
+ ref_res = await c.get(f"https://api.github.com/repos/{owner}/{repo}/git/ref/heads/{base_branch}", headers=headers)
972
+ if ref_res.status_code != 200:
973
+ ref_res = await c.get(f"https://api.github.com/repos/{owner}/{repo}/git/ref/heads/master", headers=headers)
974
+ if ref_res.status_code != 200:
975
+ return f"Error getting base branch SHA: {ref_res.text}"
976
+ sha = ref_res.json().get("object", {}).get("sha")
977
+ payload = {
978
+ "ref": f"refs/heads/{new_branch}",
979
+ "sha": sha
980
+ }
981
+ res = await c.post(f"https://api.github.com/repos/{owner}/{repo}/git/refs", headers=headers, json=payload)
982
+ if res.status_code != 201:
983
+ return f"Error creating branch: {res.text}"
984
+ return f"Successfully created branch '{new_branch}'."
985
+ except Exception as e:
986
+ return f"Error creating branch: {str(e)}"
987
+
988
+ async def github_delete_branch(user_id: str, owner: str, repo: str, branch: str):
989
+ headers = await github_get_headers(user_id)
990
+ if not headers:
991
+ return "GitHub is not connected."
992
+ try:
993
+ url = f"https://api.github.com/repos/{owner}/{repo}/git/refs/heads/{branch}"
994
+ async with httpx.AsyncClient() as c:
995
+ res = await c.delete(url, headers=headers)
996
+ if res.status_code != 204:
997
+ return f"Error deleting branch: {res.text}"
998
+ return f"Successfully deleted branch '{branch}'."
999
+ except Exception as e:
1000
+ return f"Error deleting branch: {str(e)}"
1001
+
1002
+ async def github_list_commits(user_id: str, owner: str, repo: str, path: str = None, max_results: int = 10):
1003
+ headers = await github_get_headers(user_id)
1004
+ if not headers:
1005
+ return "GitHub is not connected."
1006
+ try:
1007
+ url = f"https://api.github.com/repos/{owner}/{repo}/commits?per_page={max_results}"
1008
+ if path: url += f"&path={path}"
1009
+ async with httpx.AsyncClient() as c:
1010
+ res = await c.get(url, headers=headers)
1011
+ if res.status_code != 200:
1012
+ return f"Error fetching commits: {res.text}"
1013
+ commits = res.json()
1014
+ items = []
1015
+ for cm in commits:
1016
+ sha = cm.get("sha", "")[:7]
1017
+ msg = cm.get("commit", {}).get("message", "").split("\n")[0]
1018
+ author = cm.get("commit", {}).get("author", {}).get("name", "")
1019
+ date = cm.get("commit", {}).get("author", {}).get("date", "")
1020
+ items.append(f"Commit [{sha}]: {msg} | Author: {author} | Date: {date}")
1021
+ return "\n".join(items)
1022
+ except Exception as e:
1023
+ return f"Error listing commits: {str(e)}"
1024
+
1025
+ async def github_list_pull_requests(user_id: str, owner: str, repo: str, state: str = "open"):
1026
+ headers = await github_get_headers(user_id)
1027
+ if not headers:
1028
+ return "GitHub is not connected."
1029
+ try:
1030
+ url = f"https://api.github.com/repos/{owner}/{repo}/pulls?state={state}"
1031
+ async with httpx.AsyncClient() as c:
1032
+ res = await c.get(url, headers=headers)
1033
+ if res.status_code != 200:
1034
+ return f"Error fetching PRs: {res.text}"
1035
+ prs = res.json()
1036
+ if not prs: return "No Pull Requests found."
1037
+ items = []
1038
+ for p in prs:
1039
+ items.append(f"PR #{p.get('number')}: {p.get('title')} | State: {p.get('state')} | User: {p.get('user', {}).get('login')} | Head: {p.get('head', {}).get('ref')} -> Base: {p.get('base', {}).get('ref')}")
1040
+ return "\n".join(items)
1041
+ except Exception as e:
1042
+ return f"Error listing PRs: {str(e)}"
1043
+
1044
+ async def github_get_pull_request(user_id: str, owner: str, repo: str, pull_number: int):
1045
+ headers = await github_get_headers(user_id)
1046
+ if not headers:
1047
+ return "GitHub is not connected."
1048
+ try:
1049
+ url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pull_number}"
1050
+ async with httpx.AsyncClient() as c:
1051
+ res = await c.get(url, headers=headers)
1052
+ if res.status_code != 200:
1053
+ return f"Error fetching PR detail: {res.text}"
1054
+ p = res.json()
1055
+ return f"PR #{p.get('number')}: {p.get('title')}\nState: {p.get('state')}\nMergeable: {p.get('mergeable')}\nBody:\n{p.get('body')}"
1056
+ except Exception as e:
1057
+ return f"Error fetching PR detail: {str(e)}"
1058
+
1059
+ async def github_create_pull_request(user_id: str, owner: str, repo: str, title: str, head: str, base: str = "main", body: str = ""):
1060
+ headers = await github_get_headers(user_id)
1061
+ if not headers:
1062
+ return "GitHub is not connected."
1063
+ try:
1064
+ url = f"https://api.github.com/repos/{owner}/{repo}/pulls"
1065
+ payload = {"title": title, "head": head, "base": base, "body": body}
1066
+ async with httpx.AsyncClient() as c:
1067
+ res = await c.post(url, headers=headers, json=payload)
1068
+ if res.status_code not in [200, 201]:
1069
+ return f"Error creating PR: {res.text}"
1070
+ p = res.json()
1071
+ return f"Successfully created PR #{p.get('number')}: {p.get('html_url')}"
1072
+ except Exception as e:
1073
+ return f"Error creating PR: {str(e)}"
1074
+
1075
+ async def github_merge_pull_request(user_id: str, owner: str, repo: str, pull_number: int, commit_title: str = None):
1076
+ headers = await github_get_headers(user_id)
1077
+ if not headers:
1078
+ return "GitHub is not connected."
1079
+ try:
1080
+ url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pull_number}/merge"
1081
+ payload = {}
1082
+ if commit_title: payload["commit_title"] = commit_title
1083
+ async with httpx.AsyncClient() as c:
1084
+ res = await c.put(url, headers=headers, json=payload)
1085
+ if res.status_code != 200:
1086
+ return f"Error merging PR: {res.text}"
1087
+ return f"Successfully merged PR #{pull_number}."
1088
+ except Exception as e:
1089
+ return f"Error merging PR: {str(e)}"
1090
+
1091
  async def github_list_issues(user_id: str, owner: str, repo: str, state: str = "open"):
1092
  headers = await github_get_headers(user_id)
1093
  if not headers:
 
1124
  except Exception as e:
1125
  return f"Error creating GitHub issue: {str(e)}"
1126
 
1127
+ async def github_update_issue(user_id: str, owner: str, repo: str, issue_number: int, title: str = None, body: str = None, state: str = None):
1128
+ headers = await github_get_headers(user_id)
1129
+ if not headers:
1130
+ return "GitHub is not connected."
1131
+ try:
1132
+ url = f"https://api.github.com/repos/{owner}/{repo}/issues/{issue_number}"
1133
+ payload = {}
1134
+ if title: payload["title"] = title
1135
+ if body: payload["body"] = body
1136
+ if state: payload["state"] = state
1137
+ async with httpx.AsyncClient() as c:
1138
+ res = await c.patch(url, headers=headers, json=payload)
1139
+ if res.status_code != 200:
1140
+ return f"Error updating issue: {res.text}"
1141
+ return f"Successfully updated Issue #{issue_number}."
1142
+ except Exception as e:
1143
+ return f"Error updating issue: {str(e)}"
1144
+
1145
+ async def github_create_comment(user_id: str, owner: str, repo: str, issue_number: int, body: str):
1146
+ headers = await github_get_headers(user_id)
1147
+ if not headers:
1148
+ return "GitHub is not connected."
1149
+ try:
1150
+ url = f"https://api.github.com/repos/{owner}/{repo}/issues/{issue_number}/comments"
1151
+ payload = {"body": body}
1152
+ async with httpx.AsyncClient() as c:
1153
+ res = await c.post(url, headers=headers, json=payload)
1154
+ if res.status_code not in [200, 201]:
1155
+ return f"Error adding comment: {res.text}"
1156
+ return f"Successfully added comment to Issue/PR #{issue_number}."
1157
+ except Exception as e:
1158
+ return f"Error adding comment: {str(e)}"
1159
+
1160
+ async def github_search_code(user_id: str, query: str):
1161
+ headers = await github_get_headers(user_id)
1162
+ if not headers:
1163
+ return "GitHub is not connected."
1164
+ try:
1165
+ encoded_q = urllib.parse.quote(query)
1166
+ url = f"https://api.github.com/search/code?q={encoded_q}"
1167
+ async with httpx.AsyncClient() as c:
1168
+ res = await c.get(url, headers=headers)
1169
+ if res.status_code != 200:
1170
+ return f"Error searching code: {res.text}"
1171
+ data = res.json()
1172
+ items = data.get("items", [])
1173
+ if not items: return "No code matches found."
1174
+ results = []
1175
+ for item in items[:10]:
1176
+ results.append(f"Repo: {item.get('repository', {}).get('full_name')} | File: {item.get('path')} | URL: {item.get('html_url')}")
1177
+ return "\n".join(results)
1178
+ except Exception as e:
1179
+ return f"Error searching code: {str(e)}"
1180
+
1181
+ async def github_list_workflow_runs(user_id: str, owner: str, repo: str):
1182
+ headers = await github_get_headers(user_id)
1183
+ if not headers:
1184
+ return "GitHub is not connected."
1185
+ try:
1186
+ url = f"https://api.github.com/repos/{owner}/{repo}/actions/runs?per_page=5"
1187
+ async with httpx.AsyncClient() as c:
1188
+ res = await c.get(url, headers=headers)
1189
+ if res.status_code != 200:
1190
+ return f"Error fetching workflow runs: {res.text}"
1191
+ runs = res.json().get("workflow_runs", [])
1192
+ if not runs: return "No CI/CD workflow runs found."
1193
+ items = []
1194
+ for r in runs:
1195
+ items.append(f"Run #{r.get('run_number')}: {r.get('name')} | Status: {r.get('status')} | Conclusion: {r.get('conclusion')} | Branch: {r.get('head_branch')}")
1196
+ return "\n".join(items)
1197
+ except Exception as e:
1198
+ return f"Error fetching workflow runs: {str(e)}"
1199
+
1200
  async def notion_get_headers(user_id: str):
1201
  token_data = await get_token_from_supabase(user_id, "notion")
1202
  if not token_data:
 
1702
  elif func_name == "github_list_repos":
1703
  return await github_list_repos(user_id, args.get("max_results", 10))
1704
  elif func_name == "github_get_repo_content":
1705
+ return await github_get_repo_content(user_id, args.get("owner", ""), args.get("repo", ""), args.get("path", ""), args.get("branch"))
1706
+ elif func_name == "github_create_or_update_file":
1707
+ return await github_create_or_update_file(user_id, args.get("owner", ""), args.get("repo", ""), args.get("path", ""), args.get("content", ""), args.get("message", ""), args.get("branch"))
1708
+ elif func_name == "github_delete_file":
1709
+ return await github_delete_file(user_id, args.get("owner", ""), args.get("repo", ""), args.get("path", ""), args.get("message", ""), args.get("branch"))
1710
+ elif func_name == "github_create_branch":
1711
+ return await github_create_branch(user_id, args.get("owner", ""), args.get("repo", ""), args.get("new_branch", ""), args.get("base_branch", "main"))
1712
+ elif func_name == "github_delete_branch":
1713
+ return await github_delete_branch(user_id, args.get("owner", ""), args.get("repo", ""), args.get("branch", ""))
1714
+ elif func_name == "github_list_commits":
1715
+ return await github_list_commits(user_id, args.get("owner", ""), args.get("repo", ""), args.get("path"), args.get("max_results", 10))
1716
+ elif func_name == "github_list_pull_requests":
1717
+ return await github_list_pull_requests(user_id, args.get("owner", ""), args.get("repo", ""), args.get("state", "open"))
1718
+ elif func_name == "github_get_pull_request":
1719
+ return await github_get_pull_request(user_id, args.get("owner", ""), args.get("repo", ""), args.get("pull_number", 1))
1720
+ elif func_name == "github_create_pull_request":
1721
+ return await github_create_pull_request(user_id, args.get("owner", ""), args.get("repo", ""), args.get("title", ""), args.get("head", ""), args.get("base", "main"), args.get("body", ""))
1722
+ elif func_name == "github_merge_pull_request":
1723
+ return await github_merge_pull_request(user_id, args.get("owner", ""), args.get("repo", ""), args.get("pull_number", 1), args.get("commit_title"))
1724
  elif func_name == "github_list_issues":
1725
  return await github_list_issues(user_id, args.get("owner", ""), args.get("repo", ""), args.get("state", "open"))
1726
  elif func_name == "github_create_issue":
1727
  return await github_create_issue(user_id, args.get("owner", ""), args.get("repo", ""), args.get("title", ""), args.get("body", ""))
1728
+ elif func_name == "github_update_issue":
1729
+ return await github_update_issue(user_id, args.get("owner", ""), args.get("repo", ""), args.get("issue_number", 1), args.get("title"), args.get("body"), args.get("state"))
1730
+ elif func_name == "github_create_comment":
1731
+ return await github_create_comment(user_id, args.get("owner", ""), args.get("repo", ""), args.get("issue_number", 1), args.get("body", ""))
1732
+ elif func_name == "github_search_code":
1733
+ return await github_search_code(user_id, args.get("query", ""))
1734
+ elif func_name == "github_list_workflow_runs":
1735
+ return await github_list_workflow_runs(user_id, args.get("owner", ""), args.get("repo", ""))
1736
  return "Unknown tool function."
1737
 
1738
  def count_tokens(messages):