T7yrttrd commited on
Commit
8bc4b4d
·
verified ·
1 Parent(s): 46b8c29

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +412 -478
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 workflow"
70
  },
71
  "figma": {
72
  "client_id": "Cg79RWBHaeyW2afGJVSQi8",
@@ -137,12 +137,14 @@ 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 (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.
@@ -465,41 +467,45 @@ TOOLS = [
465
  {
466
  "type": "function",
467
  "function": {
468
- "name": "github_get_user_profile",
469
- "description": "Get GitHub user profile details including username, public repos, and bio",
470
  "parameters": {
471
  "type": "object",
472
- "properties": {}
 
 
473
  }
474
  }
475
  },
476
  {
477
  "type": "function",
478
  "function": {
479
- "name": "github_list_repos",
480
- "description": "List or search user repositories in connected GitHub account",
481
  "parameters": {
482
  "type": "object",
483
  "properties": {
484
- "max_results": {"type": "integer", "description": "Number of repositories to fetch, default 10"}
485
- }
 
 
 
 
486
  }
487
  }
488
  },
489
  {
490
  "type": "function",
491
  "function": {
492
- "name": "github_get_repo_content",
493
- "description": "Read directory files or specific file text content from a GitHub repository",
494
  "parameters": {
495
  "type": "object",
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
  },
@@ -507,18 +513,19 @@ TOOLS = [
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
  },
@@ -533,8 +540,9 @@ TOOLS = [
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
  }
@@ -543,33 +551,32 @@ TOOLS = [
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
  },
@@ -577,14 +584,14 @@ TOOLS = [
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
  }
@@ -594,50 +601,34 @@ TOOLS = [
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
  },
@@ -645,7 +636,7 @@ TOOLS = [
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": {
@@ -668,7 +659,7 @@ TOOLS = [
668
  "properties": {
669
  "owner": {"type": "string", "description": "Repository owner"},
670
  "repo": {"type": "string", "description": "Repository name"},
671
- "state": {"type": "string", "description": "State of issues e.g. 'open', 'closed', 'all'"}
672
  },
673
  "required": ["owner", "repo"]
674
  }
@@ -678,14 +669,16 @@ TOOLS = [
678
  "type": "function",
679
  "function": {
680
  "name": "github_create_issue",
681
- "description": "Create a new issue in a GitHub repository",
682
  "parameters": {
683
  "type": "object",
684
  "properties": {
685
  "owner": {"type": "string", "description": "Repository owner"},
686
  "repo": {"type": "string", "description": "Repository name"},
687
  "title": {"type": "string", "description": "Issue title"},
688
- "body": {"type": "string", "description": "Optional issue body content"}
 
 
689
  },
690
  "required": ["owner", "repo", "title"]
691
  }
@@ -695,7 +688,7 @@ TOOLS = [
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": {
@@ -703,8 +696,8 @@ TOOLS = [
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
  }
@@ -713,8 +706,8 @@ TOOLS = [
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": {
@@ -726,35 +719,6 @@ TOOLS = [
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
 
@@ -841,362 +805,6 @@ async def get_google_creds(user_id: str, service_name: str = "google"):
841
  pass
842
  return creds
843
 
844
- async def github_get_headers(user_id: str):
845
- token_data = await get_token_from_supabase(user_id, "github")
846
- if not token_data:
847
- return None
848
- access_token = token_data.get("access_token") or token_data.get("token")
849
- if not access_token:
850
- return None
851
- return {
852
- "Authorization": f"Bearer {access_token}",
853
- "Accept": "application/vnd.github+json",
854
- "User-Agent": "Spark-App"
855
- }
856
-
857
- async def github_get_user_profile(user_id: str):
858
- headers = await github_get_headers(user_id)
859
- if not headers:
860
- return "GitHub is not connected. Please connect GitHub account."
861
- try:
862
- async with httpx.AsyncClient() as c:
863
- res = await c.get("https://api.github.com/user", headers=headers)
864
- if res.status_code != 200:
865
- return f"Error fetching GitHub profile: {res.text}"
866
- data = res.json()
867
- return f"GitHub User: {data.get('login')} | Name: {data.get('name')} | Public Repos: {data.get('public_repos')} | Bio: {data.get('bio')}"
868
- except Exception as e:
869
- return f"Error fetching GitHub profile: {str(e)}"
870
-
871
- async def github_list_repos(user_id: str, max_results: int = 10):
872
- headers = await github_get_headers(user_id)
873
- if not headers:
874
- return "GitHub is not connected. Please connect GitHub account."
875
- try:
876
- async with httpx.AsyncClient() as c:
877
- res = await c.get(f"https://api.github.com/user/repos?sort=updated&per_page={max_results}", headers=headers)
878
- if res.status_code != 200:
879
- return f"Error fetching repositories: {res.text}"
880
- repos = res.json()
881
- if not repos:
882
- return "No repositories found in GitHub account."
883
- items = []
884
- for r in repos:
885
- items.append(f"Repo: {r.get('full_name')} | Private: {r.get('private')} | Stars: {r.get('stargazers_count')} | Language: {r.get('language')} | Description: {r.get('description')}")
886
- return "\n".join(items)
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:
901
- return f"Error fetching repository content: {res.text}"
902
- data = res.json()
903
- if isinstance(data, list):
904
- items = []
905
- for item in data:
906
- items.append(f"Type: {item.get('type')} | Name: {item.get('name')} | Path: {item.get('path')}")
907
- return "\n".join(items)
908
- elif isinstance(data, dict):
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:
1094
- return "GitHub is not connected."
1095
- try:
1096
- url = f"https://api.github.com/repos/{owner}/{repo}/issues?state={state}"
1097
- async with httpx.AsyncClient() as c:
1098
- res = await c.get(url, headers=headers)
1099
- if res.status_code != 200:
1100
- return f"Error fetching issues: {res.text}"
1101
- issues = res.json()
1102
- if not issues:
1103
- return "No issues found."
1104
- items = []
1105
- for i in issues:
1106
- items.append(f"Issue #{i.get('number')}: {i.get('title')} | State: {i.get('state')} | User: {i.get('user', {}).get('login')}")
1107
- return "\n".join(items)
1108
- except Exception as e:
1109
- return f"Error fetching GitHub issues: {str(e)}"
1110
-
1111
- async def github_create_issue(user_id: str, owner: str, repo: str, title: str, body: str = ""):
1112
- headers = await github_get_headers(user_id)
1113
- if not headers:
1114
- return "GitHub is not connected."
1115
- try:
1116
- url = f"https://api.github.com/repos/{owner}/{repo}/issues"
1117
- payload = {"title": title, "body": body}
1118
- async with httpx.AsyncClient() as c:
1119
- res = await c.post(url, headers=headers, json=payload)
1120
- if res.status_code not in [200, 201]:
1121
- return f"Error creating issue: {res.text}"
1122
- data = res.json()
1123
- return f"Successfully created issue #{data.get('number')}: {data.get('html_url')}"
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:
@@ -1654,6 +1262,338 @@ async def drive_update_file(user_id: str, file_id: str, content: str):
1654
  except Exception as e:
1655
  return f"Error updating Drive file: {str(e)}"
1656
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1657
  async def execute_tool_call(user_id: str, func_name: str, args: dict) -> str:
1658
  if func_name == "gmail_search_emails":
1659
  return await gmail_search_emails(user_id, args.get("query", ""), args.get("max_results", 5))
@@ -1697,42 +1637,36 @@ async def execute_tool_call(user_id: str, func_name: str, args: dict) -> str:
1697
  return await notion_delete_page(user_id, args.get("page_id", ""))
1698
  elif func_name == "notion_append_block_children":
1699
  return await notion_append_block_children(user_id, args.get("page_id", ""), args.get("content", ""))
1700
- elif func_name == "github_get_user_profile":
1701
- return await github_get_user_profile(user_id)
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):
 
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": "repo read:user user"
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:
141
+ - Always call `github_list_repos` first if you do not know the exact owner or repo name.
142
+ - To inspect repository structure or read files, use `github_get_file_or_dir`.
143
+ - To search code or find functions across repository, use `github_search_code`.
144
+ - To create or edit code/files, use `github_create_or_update_file`.
145
+ - To delete files, use `github_delete_file`.
146
+ - For Git workflow, use `github_list_branches`, `github_create_branch`, `github_list_commits`, `github_create_pull_request`, or `github_merge_pull_request`.
147
+ - For Issue management, use `github_list_issues`, `github_create_issue`, `github_update_issue`, or `github_add_issue_comment`.
148
 
149
  5. NATURAL CONVERSATION & TOOL RESPONSE STYLE:
150
  - Do NOT introduce your company, founder, or model name in general responses unless requested.
 
467
  {
468
  "type": "function",
469
  "function": {
470
+ "name": "github_list_repos",
471
+ "description": "List all GitHub repositories accessible by the user",
472
  "parameters": {
473
  "type": "object",
474
+ "properties": {
475
+ "max_results": {"type": "integer", "description": "Max repositories to list, default 10"}
476
+ }
477
  }
478
  }
479
  },
480
  {
481
  "type": "function",
482
  "function": {
483
+ "name": "github_get_file_or_dir",
484
+ "description": "Read file content or list directory contents in a GitHub repository",
485
  "parameters": {
486
  "type": "object",
487
  "properties": {
488
+ "owner": {"type": "string", "description": "Repository owner/username"},
489
+ "repo": {"type": "string", "description": "Repository name"},
490
+ "path": {"type": "string", "description": "File or folder path e.g. 'src/index.js' or '' for root"},
491
+ "ref": {"type": "string", "description": "Branch, tag, or commit SHA, default 'main'"}
492
+ },
493
+ "required": ["owner", "repo"]
494
  }
495
  }
496
  },
497
  {
498
  "type": "function",
499
  "function": {
500
+ "name": "github_search_code",
501
+ "description": "Search code, functions, or text across GitHub repositories",
502
  "parameters": {
503
  "type": "object",
504
  "properties": {
505
+ "query": {"type": "string", "description": "Code or text to search for"},
506
+ "repo": {"type": "string", "description": "Optional target repository e.g. 'owner/repo'"}
 
 
507
  },
508
+ "required": ["query"]
509
  }
510
  }
511
  },
 
513
  "type": "function",
514
  "function": {
515
  "name": "github_create_or_update_file",
516
+ "description": "Create a new file or update/edit an existing file in GitHub repository",
517
  "parameters": {
518
  "type": "object",
519
  "properties": {
520
  "owner": {"type": "string", "description": "Repository owner"},
521
  "repo": {"type": "string", "description": "Repository name"},
522
+ "path": {"type": "string", "description": "File path in repository"},
 
523
  "message": {"type": "string", "description": "Commit message"},
524
+ "content": {"type": "string", "description": "Text content of the file"},
525
+ "branch": {"type": "string", "description": "Target branch, default 'main'"},
526
+ "sha": {"type": "string", "description": "SHA of existing file if updating"}
527
  },
528
+ "required": ["owner", "repo", "path", "message", "content"]
529
  }
530
  }
531
  },
 
540
  "owner": {"type": "string", "description": "Repository owner"},
541
  "repo": {"type": "string", "description": "Repository name"},
542
  "path": {"type": "string", "description": "File path to delete"},
543
+ "message": {"type": "string", "description": "Commit message for deletion"},
544
+ "branch": {"type": "string", "description": "Target branch, default 'main'"},
545
+ "sha": {"type": "string", "description": "SHA of file to delete"}
546
  },
547
  "required": ["owner", "repo", "path", "message"]
548
  }
 
551
  {
552
  "type": "function",
553
  "function": {
554
+ "name": "github_list_branches",
555
+ "description": "List all branches in a GitHub repository",
556
  "parameters": {
557
  "type": "object",
558
  "properties": {
559
  "owner": {"type": "string", "description": "Repository owner"},
560
+ "repo": {"type": "string", "description": "Repository name"}
 
 
561
  },
562
+ "required": ["owner", "repo"]
563
  }
564
  }
565
  },
566
  {
567
  "type": "function",
568
  "function": {
569
+ "name": "github_create_branch",
570
+ "description": "Create a new branch in a GitHub repository",
571
  "parameters": {
572
  "type": "object",
573
  "properties": {
574
  "owner": {"type": "string", "description": "Repository owner"},
575
  "repo": {"type": "string", "description": "Repository name"},
576
+ "branch_name": {"type": "string", "description": "Name for new branch"},
577
+ "from_branch": {"type": "string", "description": "Source branch name, default 'main'"}
578
  },
579
+ "required": ["owner", "repo", "branch_name"]
580
  }
581
  }
582
  },
 
584
  "type": "function",
585
  "function": {
586
  "name": "github_list_commits",
587
+ "description": "List recent commits in a GitHub repository branch",
588
  "parameters": {
589
  "type": "object",
590
  "properties": {
591
  "owner": {"type": "string", "description": "Repository owner"},
592
  "repo": {"type": "string", "description": "Repository name"},
593
+ "branch": {"type": "string", "description": "Branch name, default 'main'"},
594
+ "max_results": {"type": "integer", "description": "Max commits to list, default 10"}
595
  },
596
  "required": ["owner", "repo"]
597
  }
 
601
  "type": "function",
602
  "function": {
603
  "name": "github_list_pull_requests",
604
+ "description": "List Pull Requests in a GitHub repository",
605
  "parameters": {
606
  "type": "object",
607
  "properties": {
608
  "owner": {"type": "string", "description": "Repository owner"},
609
  "repo": {"type": "string", "description": "Repository name"},
610
+ "state": {"type": "string", "description": "'open', 'closed', or 'all', default 'open'"}
611
  },
612
  "required": ["owner", "repo"]
613
  }
614
  }
615
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
616
  {
617
  "type": "function",
618
  "function": {
619
  "name": "github_create_pull_request",
620
+ "description": "Create a new Pull Request in a GitHub repository",
621
  "parameters": {
622
  "type": "object",
623
  "properties": {
624
  "owner": {"type": "string", "description": "Repository owner"},
625
  "repo": {"type": "string", "description": "Repository name"},
626
  "title": {"type": "string", "description": "PR title"},
627
+ "head": {"type": "string", "description": "Feature branch name containing changes"},
628
+ "base": {"type": "string", "description": "Target branch to merge into, default 'main'"},
629
  "body": {"type": "string", "description": "PR description"}
630
  },
631
+ "required": ["owner", "repo", "title", "head"]
632
  }
633
  }
634
  },
 
636
  "type": "function",
637
  "function": {
638
  "name": "github_merge_pull_request",
639
+ "description": "Merge a Pull Request in a GitHub repository",
640
  "parameters": {
641
  "type": "object",
642
  "properties": {
 
659
  "properties": {
660
  "owner": {"type": "string", "description": "Repository owner"},
661
  "repo": {"type": "string", "description": "Repository name"},
662
+ "state": {"type": "string", "description": "'open', 'closed', or 'all', default 'open'"}
663
  },
664
  "required": ["owner", "repo"]
665
  }
 
669
  "type": "function",
670
  "function": {
671
  "name": "github_create_issue",
672
+ "description": "Create a new Issue in a GitHub repository",
673
  "parameters": {
674
  "type": "object",
675
  "properties": {
676
  "owner": {"type": "string", "description": "Repository owner"},
677
  "repo": {"type": "string", "description": "Repository name"},
678
  "title": {"type": "string", "description": "Issue title"},
679
+ "body": {"type": "string", "description": "Issue body content"},
680
+ "labels": {"type": "array", "items": {"type": "string"}, "description": "Labels list"},
681
+ "assignees": {"type": "array", "items": {"type": "string"}, "description": "Usernames to assign"}
682
  },
683
  "required": ["owner", "repo", "title"]
684
  }
 
688
  "type": "function",
689
  "function": {
690
  "name": "github_update_issue",
691
+ "description": "Update or close an Issue in GitHub repository",
692
  "parameters": {
693
  "type": "object",
694
  "properties": {
 
696
  "repo": {"type": "string", "description": "Repository name"},
697
  "issue_number": {"type": "integer", "description": "Issue number"},
698
  "title": {"type": "string", "description": "New title"},
699
+ "body": {"type": "string", "description": "New body"},
700
+ "state": {"type": "string", "description": "'open' or 'closed'"}
701
  },
702
  "required": ["owner", "repo", "issue_number"]
703
  }
 
706
  {
707
  "type": "function",
708
  "function": {
709
+ "name": "github_add_issue_comment",
710
+ "description": "Add a comment to an Issue or PR in GitHub",
711
  "parameters": {
712
  "type": "object",
713
  "properties": {
 
719
  "required": ["owner", "repo", "issue_number", "body"]
720
  }
721
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
722
  }
723
  ]
724
 
 
805
  pass
806
  return creds
807
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
808
  async def notion_get_headers(user_id: str):
809
  token_data = await get_token_from_supabase(user_id, "notion")
810
  if not token_data:
 
1262
  except Exception as e:
1263
  return f"Error updating Drive file: {str(e)}"
1264
 
1265
+ async def github_get_headers(user_id: str):
1266
+ token_data = await get_token_from_supabase(user_id, "github")
1267
+ if not token_data:
1268
+ return None
1269
+ token = token_data.get("access_token") or token_data.get("token")
1270
+ if not token:
1271
+ return None
1272
+ return {
1273
+ "Authorization": f"Bearer {token}",
1274
+ "Accept": "application/vnd.github.v3+json",
1275
+ "User-Agent": "Spark-App"
1276
+ }
1277
+
1278
+ async def github_list_repos(user_id: str, max_results: int = 10):
1279
+ headers = await github_get_headers(user_id)
1280
+ if not headers:
1281
+ return "GitHub is not connected. Please connect your GitHub account."
1282
+ try:
1283
+ url = f"https://api.github.com/user/repos?sort=updated&per_page={max_results}"
1284
+ async with httpx.AsyncClient() as c:
1285
+ res = await c.get(url, headers=headers)
1286
+ if res.status_code != 200:
1287
+ return f"Error fetching GitHub repositories: {res.text}"
1288
+ data = res.json()
1289
+ if not data:
1290
+ return "No repositories found in user's GitHub account."
1291
+ repos = []
1292
+ for r in data:
1293
+ repos.append(f"Repo: {r.get('full_name')} | Default Branch: {r.get('default_branch', 'main')} | Private: {r.get('private')} | URL: {r.get('html_url')}")
1294
+ return "\n".join(repos)
1295
+ except Exception as e:
1296
+ return f"Error fetching GitHub repositories: {str(e)}"
1297
+
1298
+ async def github_get_file_or_dir(user_id: str, owner: str, repo: str, path: str = "", ref: str = "main"):
1299
+ headers = await github_get_headers(user_id)
1300
+ if not headers:
1301
+ return "GitHub is not connected."
1302
+ try:
1303
+ url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}?ref={ref}"
1304
+ async with httpx.AsyncClient() as c:
1305
+ res = await c.get(url, headers=headers)
1306
+ if res.status_code != 200:
1307
+ return f"Error reading GitHub path '{path}': {res.text}"
1308
+ data = res.json()
1309
+ if isinstance(data, list):
1310
+ items = []
1311
+ for item in data:
1312
+ items.append(f"[{item.get('type')}] {item.get('name')} (Path: {item.get('path')})")
1313
+ return "\n".join(items)
1314
+ elif isinstance(data, dict):
1315
+ content_b64 = data.get("content", "")
1316
+ if content_b64:
1317
+ decoded = base64.b64decode(content_b64.encode()).decode("utf-8", errors="ignore")
1318
+ sha = data.get("sha", "")
1319
+ return f"File: {data.get('path')} | SHA: {sha}\n\nContent:\n{decoded}"
1320
+ return f"File metadata retrieved (Path: {data.get('path')}, SHA: {data.get('sha')}). Content unavailable or empty."
1321
+ return "Unknown content structure."
1322
+ except Exception as e:
1323
+ return f"Error accessing GitHub repository: {str(e)}"
1324
+
1325
+ async def github_search_code(user_id: str, query: str, repo: str = None):
1326
+ headers = await github_get_headers(user_id)
1327
+ if not headers:
1328
+ return "GitHub is not connected."
1329
+ try:
1330
+ q = f"{query}+repo:{repo}" if repo else query
1331
+ url = f"https://api.github.com/search/code?q={q}"
1332
+ async with httpx.AsyncClient() as c:
1333
+ res = await c.get(url, headers=headers)
1334
+ if res.status_code != 200:
1335
+ return f"Error searching GitHub code: {res.text}"
1336
+ data = res.json()
1337
+ items = data.get("items", [])
1338
+ if not items:
1339
+ return f"No code found matching query: '{query}'"
1340
+ results = []
1341
+ for item in items[:10]:
1342
+ results.append(f"Repo: {item.get('repository', {}).get('full_name')} | File: {item.get('path')} | URL: {item.get('html_url')}")
1343
+ return "\n".join(results)
1344
+ except Exception as e:
1345
+ return f"Error searching code: {str(e)}"
1346
+
1347
+ async def github_create_or_update_file(user_id: str, owner: str, repo: str, path: str, message: str, content: str, branch: str = "main", sha: str = None):
1348
+ headers = await github_get_headers(user_id)
1349
+ if not headers:
1350
+ return "GitHub is not connected."
1351
+ try:
1352
+ url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}"
1353
+ if not sha:
1354
+ async with httpx.AsyncClient() as c:
1355
+ get_res = await c.get(f"{url}?ref={branch}", headers=headers)
1356
+ if get_res.status_code == 200:
1357
+ sha = get_res.json().get("sha")
1358
+
1359
+ encoded_content = base64.b64encode(content.encode("utf-8")).decode("utf-8")
1360
+ payload = {
1361
+ "message": message,
1362
+ "content": encoded_content,
1363
+ "branch": branch
1364
+ }
1365
+ if sha:
1366
+ payload["sha"] = sha
1367
+
1368
+ async with httpx.AsyncClient() as c:
1369
+ res = await c.put(url, headers=headers, json=payload)
1370
+ if res.status_code not in [200, 201]:
1371
+ return f"Error committing file to GitHub: {res.text}"
1372
+ res_data = res.json()
1373
+ commit_sha = res_data.get("commit", {}).get("sha", "")
1374
+ return f"Successfully committed file '{path}' to branch '{branch}' (Commit SHA: {commit_sha})."
1375
+ except Exception as e:
1376
+ return f"Error writing file to GitHub: {str(e)}"
1377
+
1378
+ async def github_delete_file(user_id: str, owner: str, repo: str, path: str, message: str, branch: str = "main", sha: str = None):
1379
+ headers = await github_get_headers(user_id)
1380
+ if not headers:
1381
+ return "GitHub is not connected."
1382
+ try:
1383
+ url = f"https://api.github.com/repos/{owner}/{repo}/contents/{path}"
1384
+ if not sha:
1385
+ async with httpx.AsyncClient() as c:
1386
+ get_res = await c.get(f"{url}?ref={branch}", headers=headers)
1387
+ if get_res.status_code == 200:
1388
+ sha = get_res.json().get("sha")
1389
+ else:
1390
+ return f"Cannot find file '{path}' to delete."
1391
+
1392
+ payload = {
1393
+ "message": message,
1394
+ "sha": sha,
1395
+ "branch": branch
1396
+ }
1397
+ async with httpx.AsyncClient() as c:
1398
+ res = await c.request("DELETE", url, headers=headers, json=payload)
1399
+ if res.status_code != 200:
1400
+ return f"Error deleting file from GitHub: {res.text}"
1401
+ return f"Successfully deleted file '{path}' from branch '{branch}'."
1402
+ except Exception as e:
1403
+ return f"Error deleting GitHub file: {str(e)}"
1404
+
1405
+ async def github_list_branches(user_id: str, owner: str, repo: str):
1406
+ headers = await github_get_headers(user_id)
1407
+ if not headers:
1408
+ return "GitHub is not connected."
1409
+ try:
1410
+ url = f"https://api.github.com/repos/{owner}/{repo}/branches"
1411
+ async with httpx.AsyncClient() as c:
1412
+ res = await c.get(url, headers=headers)
1413
+ if res.status_code != 200:
1414
+ return f"Error listing branches: {res.text}"
1415
+ data = res.json()
1416
+ branches = [f"Branch: {b.get('name')} | SHA: {b.get('commit', {}).get('sha')}" for b in data]
1417
+ return "\n".join(branches) if branches else "No branches found."
1418
+ except Exception as e:
1419
+ return f"Error listing branches: {str(e)}"
1420
+
1421
+ async def github_create_branch(user_id: str, owner: str, repo: str, branch_name: str, from_branch: str = "main"):
1422
+ headers = await github_get_headers(user_id)
1423
+ if not headers:
1424
+ return "GitHub is not connected."
1425
+ try:
1426
+ async with httpx.AsyncClient() as c:
1427
+ ref_res = await c.get(f"https://api.github.com/repos/{owner}/{repo}/git/ref/heads/{from_branch}", headers=headers)
1428
+ if ref_res.status_code != 200:
1429
+ return f"Error getting reference SHA for branch '{from_branch}': {ref_res.text}"
1430
+ source_sha = ref_res.json().get("object", {}).get("sha")
1431
+
1432
+ payload = {
1433
+ "ref": f"refs/heads/{branch_name}",
1434
+ "sha": source_sha
1435
+ }
1436
+ res = await c.post(f"https://api.github.com/repos/{owner}/{repo}/git/refs", headers=headers, json=payload)
1437
+ if res.status_code != 201:
1438
+ return f"Error creating branch: {res.text}"
1439
+ return f"Successfully created branch '{branch_name}' from '{from_branch}'."
1440
+ except Exception as e:
1441
+ return f"Error creating branch: {str(e)}"
1442
+
1443
+ async def github_list_commits(user_id: str, owner: str, repo: str, branch: str = "main", max_results: int = 10):
1444
+ headers = await github_get_headers(user_id)
1445
+ if not headers:
1446
+ return "GitHub is not connected."
1447
+ try:
1448
+ url = f"https://api.github.com/repos/{owner}/{repo}/commits?sha={branch}&per_page={max_results}"
1449
+ async with httpx.AsyncClient() as c:
1450
+ res = await c.get(url, headers=headers)
1451
+ if res.status_code != 200:
1452
+ return f"Error fetching commits: {res.text}"
1453
+ data = res.json()
1454
+ commits = []
1455
+ for cm in data:
1456
+ sha = cm.get("sha", "")[:7]
1457
+ msg = cm.get("commit", {}).get("message", "").split("\n")[0]
1458
+ author = cm.get("commit", {}).get("author", {}).get("name", "Unknown")
1459
+ commits.append(f"Commit: {sha} | Author: {author} | Message: {msg}")
1460
+ return "\n".join(commits) if commits else "No commits found."
1461
+ except Exception as e:
1462
+ return f"Error fetching commits: {str(e)}"
1463
+
1464
+ async def github_list_pull_requests(user_id: str, owner: str, repo: str, state: str = "open"):
1465
+ headers = await github_get_headers(user_id)
1466
+ if not headers:
1467
+ return "GitHub is not connected."
1468
+ try:
1469
+ url = f"https://api.github.com/repos/{owner}/{repo}/pulls?state={state}"
1470
+ async with httpx.AsyncClient() as c:
1471
+ res = await c.get(url, headers=headers)
1472
+ if res.status_code != 200:
1473
+ return f"Error fetching pull requests: {res.text}"
1474
+ data = res.json()
1475
+ prs = []
1476
+ for pr in data:
1477
+ prs.append(f"PR #{pr.get('number')}: {pr.get('title')} | State: {pr.get('state')} | Head: {pr.get('head', {}).get('ref')} -> Base: {pr.get('base', {}).get('ref')}")
1478
+ return "\n".join(prs) if prs else f"No {state} Pull Requests found."
1479
+ except Exception as e:
1480
+ return f"Error fetching Pull Requests: {str(e)}"
1481
+
1482
+ async def github_create_pull_request(user_id: str, owner: str, repo: str, title: str, head: str, base: str = "main", body: str = ""):
1483
+ headers = await github_get_headers(user_id)
1484
+ if not headers:
1485
+ return "GitHub is not connected."
1486
+ try:
1487
+ url = f"https://api.github.com/repos/{owner}/{repo}/pulls"
1488
+ payload = {
1489
+ "title": title,
1490
+ "head": head,
1491
+ "base": base,
1492
+ "body": body
1493
+ }
1494
+ async with httpx.AsyncClient() as c:
1495
+ res = await c.post(url, headers=headers, json=payload)
1496
+ if res.status_code != 201:
1497
+ return f"Error creating Pull Request: {res.text}"
1498
+ data = res.json()
1499
+ return f"Successfully created Pull Request #{data.get('number')} '{title}' (URL: {data.get('html_url')})."
1500
+ except Exception as e:
1501
+ return f"Error creating Pull Request: {str(e)}"
1502
+
1503
+ async def github_merge_pull_request(user_id: str, owner: str, repo: str, pull_number: int, commit_title: str = ""):
1504
+ headers = await github_get_headers(user_id)
1505
+ if not headers:
1506
+ return "GitHub is not connected."
1507
+ try:
1508
+ url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pull_number}/merge"
1509
+ payload = {}
1510
+ if commit_title:
1511
+ payload["commit_title"] = commit_title
1512
+ async with httpx.AsyncClient() as c:
1513
+ res = await c.put(url, headers=headers, json=payload)
1514
+ if res.status_code != 200:
1515
+ return f"Error merging Pull Request: {res.text}"
1516
+ return f"Successfully merged Pull Request #{pull_number}."
1517
+ except Exception as e:
1518
+ return f"Error merging Pull Request: {str(e)}"
1519
+
1520
+ async def github_list_issues(user_id: str, owner: str, repo: str, state: str = "open"):
1521
+ headers = await github_get_headers(user_id)
1522
+ if not headers:
1523
+ return "GitHub is not connected."
1524
+ try:
1525
+ url = f"https://api.github.com/repos/{owner}/{repo}/issues?state={state}"
1526
+ async with httpx.AsyncClient() as c:
1527
+ res = await c.get(url, headers=headers)
1528
+ if res.status_code != 200:
1529
+ return f"Error fetching issues: {res.text}"
1530
+ data = res.json()
1531
+ issues = []
1532
+ for issue in data:
1533
+ if "pull_request" not in issue:
1534
+ issues.append(f"Issue #{issue.get('number')}: {issue.get('title')} | State: {issue.get('state')} | Author: {issue.get('user', {}).get('login')}")
1535
+ return "\n".join(issues) if issues else f"No {state} issues found."
1536
+ except Exception as e:
1537
+ return f"Error fetching issues: {str(e)}"
1538
+
1539
+ async def github_create_issue(user_id: str, owner: str, repo: str, title: str, body: str = "", labels: list = None, assignees: list = None):
1540
+ headers = await github_get_headers(user_id)
1541
+ if not headers:
1542
+ return "GitHub is not connected."
1543
+ try:
1544
+ url = f"https://api.github.com/repos/{owner}/{repo}/issues"
1545
+ payload = {
1546
+ "title": title,
1547
+ "body": body
1548
+ }
1549
+ if labels:
1550
+ payload["labels"] = labels
1551
+ if assignees:
1552
+ payload["assignees"] = assignees
1553
+
1554
+ async with httpx.AsyncClient() as c:
1555
+ res = await c.post(url, headers=headers, json=payload)
1556
+ if res.status_code != 201:
1557
+ return f"Error creating issue: {res.text}"
1558
+ data = res.json()
1559
+ return f"Successfully created Issue #{data.get('number')} '{title}' (URL: {data.get('html_url')})."
1560
+ except Exception as e:
1561
+ return f"Error creating GitHub issue: {str(e)}"
1562
+
1563
+ async def github_update_issue(user_id: str, owner: str, repo: str, issue_number: int, title: str = None, body: str = None, state: str = None):
1564
+ headers = await github_get_headers(user_id)
1565
+ if not headers:
1566
+ return "GitHub is not connected."
1567
+ try:
1568
+ url = f"https://api.github.com/repos/{owner}/{repo}/issues/{issue_number}"
1569
+ payload = {}
1570
+ if title: payload["title"] = title
1571
+ if body: payload["body"] = body
1572
+ if state: payload["state"] = state
1573
+
1574
+ async with httpx.AsyncClient() as c:
1575
+ res = await c.patch(url, headers=headers, json=payload)
1576
+ if res.status_code != 200:
1577
+ return f"Error updating issue: {res.text}"
1578
+ return f"Successfully updated Issue #{issue_number}."
1579
+ except Exception as e:
1580
+ return f"Error updating GitHub issue: {str(e)}"
1581
+
1582
+ async def github_add_issue_comment(user_id: str, owner: str, repo: str, issue_number: int, body: str):
1583
+ headers = await github_get_headers(user_id)
1584
+ if not headers:
1585
+ return "GitHub is not connected."
1586
+ try:
1587
+ url = f"https://api.github.com/repos/{owner}/{repo}/issues/{issue_number}/comments"
1588
+ payload = {"body": body}
1589
+ async with httpx.AsyncClient() as c:
1590
+ res = await c.post(url, headers=headers, json=payload)
1591
+ if res.status_code != 201:
1592
+ return f"Error adding comment: {res.text}"
1593
+ return f"Successfully commented on Issue/PR #{issue_number}."
1594
+ except Exception as e:
1595
+ return f"Error commenting on GitHub issue: {str(e)}"
1596
+
1597
  async def execute_tool_call(user_id: str, func_name: str, args: dict) -> str:
1598
  if func_name == "gmail_search_emails":
1599
  return await gmail_search_emails(user_id, args.get("query", ""), args.get("max_results", 5))
 
1637
  return await notion_delete_page(user_id, args.get("page_id", ""))
1638
  elif func_name == "notion_append_block_children":
1639
  return await notion_append_block_children(user_id, args.get("page_id", ""), args.get("content", ""))
 
 
1640
  elif func_name == "github_list_repos":
1641
  return await github_list_repos(user_id, args.get("max_results", 10))
1642
+ elif func_name == "github_get_file_or_dir":
1643
+ return await github_get_file_or_dir(user_id, args.get("owner", ""), args.get("repo", ""), args.get("path", ""), args.get("ref", "main"))
1644
+ elif func_name == "github_search_code":
1645
+ return await github_search_code(user_id, args.get("query", ""), args.get("repo"))
1646
  elif func_name == "github_create_or_update_file":
1647
+ return await github_create_or_update_file(user_id, args.get("owner", ""), args.get("repo", ""), args.get("path", ""), args.get("message", ""), args.get("content", ""), args.get("branch", "main"), args.get("sha"))
1648
  elif func_name == "github_delete_file":
1649
+ return await github_delete_file(user_id, args.get("owner", ""), args.get("repo", ""), args.get("path", ""), args.get("message", ""), args.get("branch", "main"), args.get("sha"))
1650
+ elif func_name == "github_list_branches":
1651
+ return await github_list_branches(user_id, args.get("owner", ""), args.get("repo", ""))
1652
  elif func_name == "github_create_branch":
1653
+ return await github_create_branch(user_id, args.get("owner", ""), args.get("repo", ""), args.get("branch_name", ""), args.get("from_branch", "main"))
 
 
1654
  elif func_name == "github_list_commits":
1655
+ return await github_list_commits(user_id, args.get("owner", ""), args.get("repo", ""), args.get("branch", "main"), args.get("max_results", 10))
1656
  elif func_name == "github_list_pull_requests":
1657
  return await github_list_pull_requests(user_id, args.get("owner", ""), args.get("repo", ""), args.get("state", "open"))
 
 
1658
  elif func_name == "github_create_pull_request":
1659
  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", ""))
1660
  elif func_name == "github_merge_pull_request":
1661
+ return await github_merge_pull_request(user_id, args.get("owner", ""), args.get("repo", ""), args.get("pull_number", 0), args.get("commit_title", ""))
1662
  elif func_name == "github_list_issues":
1663
  return await github_list_issues(user_id, args.get("owner", ""), args.get("repo", ""), args.get("state", "open"))
1664
  elif func_name == "github_create_issue":
1665
+ return await github_create_issue(user_id, args.get("owner", ""), args.get("repo", ""), args.get("title", ""), args.get("body", ""), args.get("labels"), args.get("assignees"))
1666
  elif func_name == "github_update_issue":
1667
+ return await github_update_issue(user_id, args.get("owner", ""), args.get("repo", ""), args.get("issue_number", 0), args.get("title"), args.get("body"), args.get("state"))
1668
+ elif func_name == "github_add_issue_comment":
1669
+ return await github_add_issue_comment(user_id, args.get("owner", ""), args.get("repo", ""), args.get("issue_number", 0), args.get("body", ""))
 
 
 
 
1670
  return "Unknown tool function."
1671
 
1672
  def count_tokens(messages):