elizaos-plugin-github 2.0.0a4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- elizaos_plugin_github/__init__.py +113 -0
- elizaos_plugin_github/actions/__init__.py +27 -0
- elizaos_plugin_github/actions/create_branch.py +62 -0
- elizaos_plugin_github/actions/create_comment.py +67 -0
- elizaos_plugin_github/actions/create_issue.py +93 -0
- elizaos_plugin_github/actions/create_pull_request.py +73 -0
- elizaos_plugin_github/actions/merge_pull_request.py +82 -0
- elizaos_plugin_github/actions/push_code.py +83 -0
- elizaos_plugin_github/actions/review_pull_request.py +84 -0
- elizaos_plugin_github/config.py +68 -0
- elizaos_plugin_github/error.py +165 -0
- elizaos_plugin_github/generated/specs/__init__.py +1 -0
- elizaos_plugin_github/generated/specs/specs.py +171 -0
- elizaos_plugin_github/providers/__init__.py +12 -0
- elizaos_plugin_github/providers/issue_context.py +136 -0
- elizaos_plugin_github/providers/repository_state.py +87 -0
- elizaos_plugin_github/py.typed +0 -0
- elizaos_plugin_github/service.py +716 -0
- elizaos_plugin_github/types.py +413 -0
- elizaos_plugin_github-2.0.0a4.dist-info/METADATA +178 -0
- elizaos_plugin_github-2.0.0a4.dist-info/RECORD +22 -0
- elizaos_plugin_github-2.0.0a4.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, field_validator
|
|
4
|
+
|
|
5
|
+
from elizaos_plugin_github.error import ConfigError, MissingSettingError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GitHubConfig(BaseModel):
|
|
9
|
+
model_config = ConfigDict(frozen=True)
|
|
10
|
+
|
|
11
|
+
# Required fields
|
|
12
|
+
api_token: str
|
|
13
|
+
|
|
14
|
+
# Optional fields with defaults
|
|
15
|
+
owner: str | None = None
|
|
16
|
+
repo: str | None = None
|
|
17
|
+
branch: str = "main"
|
|
18
|
+
webhook_secret: str | None = None
|
|
19
|
+
app_id: str | None = None
|
|
20
|
+
app_private_key: str | None = None
|
|
21
|
+
installation_id: str | None = None
|
|
22
|
+
|
|
23
|
+
@field_validator("api_token")
|
|
24
|
+
@classmethod
|
|
25
|
+
def validate_api_token(cls, v: str) -> str:
|
|
26
|
+
if not v or not v.strip():
|
|
27
|
+
raise ConfigError("API token cannot be empty")
|
|
28
|
+
return v
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_env(cls) -> "GitHubConfig":
|
|
32
|
+
api_token = os.environ.get("GITHUB_API_TOKEN")
|
|
33
|
+
if not api_token:
|
|
34
|
+
raise MissingSettingError("GITHUB_API_TOKEN")
|
|
35
|
+
|
|
36
|
+
return cls(
|
|
37
|
+
api_token=api_token,
|
|
38
|
+
owner=os.environ.get("GITHUB_OWNER"),
|
|
39
|
+
repo=os.environ.get("GITHUB_REPO"),
|
|
40
|
+
branch=os.environ.get("GITHUB_BRANCH", "main"),
|
|
41
|
+
webhook_secret=os.environ.get("GITHUB_WEBHOOK_SECRET"),
|
|
42
|
+
app_id=os.environ.get("GITHUB_APP_ID"),
|
|
43
|
+
app_private_key=os.environ.get("GITHUB_APP_PRIVATE_KEY"),
|
|
44
|
+
installation_id=os.environ.get("GITHUB_INSTALLATION_ID"),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def get_repository_ref(
|
|
48
|
+
self, owner: str | None = None, repo: str | None = None
|
|
49
|
+
) -> tuple[str, str]:
|
|
50
|
+
resolved_owner = owner or self.owner
|
|
51
|
+
resolved_repo = repo or self.repo
|
|
52
|
+
|
|
53
|
+
if not resolved_owner:
|
|
54
|
+
raise MissingSettingError("owner (GITHUB_OWNER)")
|
|
55
|
+
|
|
56
|
+
if not resolved_repo:
|
|
57
|
+
raise MissingSettingError("repo (GITHUB_REPO)")
|
|
58
|
+
|
|
59
|
+
return resolved_owner, resolved_repo
|
|
60
|
+
|
|
61
|
+
def has_app_auth(self) -> bool:
|
|
62
|
+
return bool(self.app_id and self.app_private_key)
|
|
63
|
+
|
|
64
|
+
def validate_all(self) -> None:
|
|
65
|
+
if self.has_app_auth() and not self.installation_id:
|
|
66
|
+
raise ConfigError(
|
|
67
|
+
"GITHUB_INSTALLATION_ID is required when using GitHub App authentication"
|
|
68
|
+
)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class GitHubError(Exception):
|
|
5
|
+
def __init__(self, message: str) -> None:
|
|
6
|
+
self.message = message
|
|
7
|
+
super().__init__(message)
|
|
8
|
+
|
|
9
|
+
def is_retryable(self) -> bool:
|
|
10
|
+
return False
|
|
11
|
+
|
|
12
|
+
def retry_after_ms(self) -> int | None:
|
|
13
|
+
return None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ClientNotInitializedError(GitHubError):
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
super().__init__("GitHub client not initialized - ensure GITHUB_API_TOKEN is configured")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ConfigError(GitHubError):
|
|
22
|
+
def __init__(self, message: str) -> None:
|
|
23
|
+
super().__init__(f"Configuration error: {message}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MissingSettingError(GitHubError):
|
|
27
|
+
def __init__(self, setting_name: str) -> None:
|
|
28
|
+
self.setting_name = setting_name
|
|
29
|
+
super().__init__(f"Missing required setting: {setting_name}")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class InvalidArgumentError(GitHubError):
|
|
33
|
+
def __init__(self, message: str) -> None:
|
|
34
|
+
super().__init__(f"Invalid argument: {message}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class RepositoryNotFoundError(GitHubError):
|
|
38
|
+
def __init__(self, owner: str, repo: str) -> None:
|
|
39
|
+
self.owner = owner
|
|
40
|
+
self.repo = repo
|
|
41
|
+
super().__init__(f"Repository not found: {owner}/{repo}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BranchNotFoundError(GitHubError):
|
|
45
|
+
def __init__(self, branch: str, owner: str, repo: str) -> None:
|
|
46
|
+
self.branch = branch
|
|
47
|
+
super().__init__(f"Branch not found: {branch} in {owner}/{repo}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class FileNotFoundError(GitHubError):
|
|
51
|
+
def __init__(self, path: str, owner: str, repo: str) -> None:
|
|
52
|
+
self.path = path
|
|
53
|
+
super().__init__(f"File not found: {path} in {owner}/{repo}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class IssueNotFoundError(GitHubError):
|
|
57
|
+
def __init__(self, issue_number: int, owner: str, repo: str) -> None:
|
|
58
|
+
self.issue_number = issue_number
|
|
59
|
+
super().__init__(f"Issue #{issue_number} not found in {owner}/{repo}")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class PullRequestNotFoundError(GitHubError):
|
|
63
|
+
def __init__(self, pull_number: int, owner: str, repo: str) -> None:
|
|
64
|
+
self.pull_number = pull_number
|
|
65
|
+
super().__init__(f"Pull request #{pull_number} not found in {owner}/{repo}")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class PermissionDeniedError(GitHubError):
|
|
69
|
+
def __init__(self, action: str) -> None:
|
|
70
|
+
super().__init__(f"Permission denied: {action}")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class RateLimitedError(GitHubError):
|
|
74
|
+
def __init__(self, retry_after_ms: int, remaining: int, reset_at: datetime) -> None:
|
|
75
|
+
self._retry_after_ms = retry_after_ms
|
|
76
|
+
self.remaining = remaining
|
|
77
|
+
self.reset_at = reset_at
|
|
78
|
+
super().__init__(f"Rate limited by GitHub API, retry after {retry_after_ms // 1000}s")
|
|
79
|
+
|
|
80
|
+
def is_retryable(self) -> bool:
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
def retry_after_ms(self) -> int | None:
|
|
84
|
+
return self._retry_after_ms
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class SecondaryRateLimitError(GitHubError):
|
|
88
|
+
def __init__(self, retry_after_ms: int) -> None:
|
|
89
|
+
self._retry_after_ms = retry_after_ms
|
|
90
|
+
super().__init__(f"Secondary rate limit hit, retry after {retry_after_ms // 1000}s")
|
|
91
|
+
|
|
92
|
+
def is_retryable(self) -> bool:
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
def retry_after_ms(self) -> int | None:
|
|
96
|
+
return self._retry_after_ms
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class TimeoutError(GitHubError):
|
|
100
|
+
def __init__(self, timeout_ms: int, operation: str) -> None:
|
|
101
|
+
self._timeout_ms = timeout_ms
|
|
102
|
+
super().__init__(f"Operation timed out after {timeout_ms}ms: {operation}")
|
|
103
|
+
|
|
104
|
+
def is_retryable(self) -> bool:
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
def retry_after_ms(self) -> int | None:
|
|
108
|
+
return self._timeout_ms // 2
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class MergeConflictError(GitHubError):
|
|
112
|
+
def __init__(self, pull_number: int, owner: str, repo: str) -> None:
|
|
113
|
+
self.pull_number = pull_number
|
|
114
|
+
super().__init__(f"Merge conflict in pull request #{pull_number} in {owner}/{repo}")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class BranchExistsError(GitHubError):
|
|
118
|
+
def __init__(self, branch: str, owner: str, repo: str) -> None:
|
|
119
|
+
self.branch = branch
|
|
120
|
+
super().__init__(f"Branch already exists: {branch} in {owner}/{repo}")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class ValidationError(GitHubError):
|
|
124
|
+
def __init__(self, field: str, reason: str) -> None:
|
|
125
|
+
self.field = field
|
|
126
|
+
super().__init__(f"Validation failed for {field}: {reason}")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class GitHubApiError(GitHubError):
|
|
130
|
+
def __init__(
|
|
131
|
+
self,
|
|
132
|
+
status: int,
|
|
133
|
+
message: str,
|
|
134
|
+
code: str | None = None,
|
|
135
|
+
documentation_url: str | None = None,
|
|
136
|
+
) -> None:
|
|
137
|
+
self.status = status
|
|
138
|
+
self.code = code
|
|
139
|
+
self.documentation_url = documentation_url
|
|
140
|
+
super().__init__(f"GitHub API error ({status}): {message}")
|
|
141
|
+
|
|
142
|
+
def is_retryable(self) -> bool:
|
|
143
|
+
return self.status >= 500
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class NetworkError(GitHubError):
|
|
147
|
+
def __init__(self, message: str) -> None:
|
|
148
|
+
super().__init__(f"Network error: {message}")
|
|
149
|
+
|
|
150
|
+
def is_retryable(self) -> bool:
|
|
151
|
+
return True
|
|
152
|
+
|
|
153
|
+
def retry_after_ms(self) -> int | None:
|
|
154
|
+
return 1000
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class GitOperationError(GitHubError):
|
|
158
|
+
def __init__(self, operation: str, reason: str) -> None:
|
|
159
|
+
self.operation = operation
|
|
160
|
+
super().__init__(f"Git operation failed ({operation}): {reason}")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class WebhookVerificationError(GitHubError):
|
|
164
|
+
def __init__(self, reason: str) -> None:
|
|
165
|
+
super().__init__(f"Webhook verification failed: {reason}")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Auto-generated module package."""
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auto-generated canonical action/provider/evaluator docs for plugin-github.
|
|
3
|
+
DO NOT EDIT - Generated from prompts/specs/**.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from typing import TypedDict
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ActionDoc(TypedDict, total=False):
|
|
13
|
+
name: str
|
|
14
|
+
description: str
|
|
15
|
+
similes: list[str]
|
|
16
|
+
parameters: list[object]
|
|
17
|
+
examples: list[list[object]]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ProviderDoc(TypedDict, total=False):
|
|
21
|
+
name: str
|
|
22
|
+
description: str
|
|
23
|
+
position: int
|
|
24
|
+
dynamic: bool
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class EvaluatorDoc(TypedDict, total=False):
|
|
28
|
+
name: str
|
|
29
|
+
description: str
|
|
30
|
+
similes: list[str]
|
|
31
|
+
alwaysRun: bool
|
|
32
|
+
examples: list[object]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
_CORE_ACTION_DOCS_JSON = """{
|
|
36
|
+
"version": "1.0.0",
|
|
37
|
+
"actions": [
|
|
38
|
+
{
|
|
39
|
+
"name": "CREATE_GITHUB_BRANCH",
|
|
40
|
+
"description": "",
|
|
41
|
+
"parameters": []
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "CREATE_GITHUB_COMMENT",
|
|
45
|
+
"description": "",
|
|
46
|
+
"parameters": []
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "CREATE_GITHUB_ISSUE",
|
|
50
|
+
"description": "",
|
|
51
|
+
"parameters": []
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "CREATE_GITHUB_PULL_REQUEST",
|
|
55
|
+
"description": "",
|
|
56
|
+
"parameters": []
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "MERGE_GITHUB_PULL_REQUEST",
|
|
60
|
+
"description": "",
|
|
61
|
+
"parameters": []
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "PUSH_GITHUB_CODE",
|
|
65
|
+
"description": "",
|
|
66
|
+
"parameters": []
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "REVIEW_GITHUB_PULL_REQUEST",
|
|
70
|
+
"description": "",
|
|
71
|
+
"parameters": []
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}"""
|
|
75
|
+
_ALL_ACTION_DOCS_JSON = """{
|
|
76
|
+
"version": "1.0.0",
|
|
77
|
+
"actions": [
|
|
78
|
+
{
|
|
79
|
+
"name": "CREATE_GITHUB_BRANCH",
|
|
80
|
+
"description": "",
|
|
81
|
+
"parameters": []
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"name": "CREATE_GITHUB_COMMENT",
|
|
85
|
+
"description": "",
|
|
86
|
+
"parameters": []
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "CREATE_GITHUB_ISSUE",
|
|
90
|
+
"description": "",
|
|
91
|
+
"parameters": []
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"name": "CREATE_GITHUB_PULL_REQUEST",
|
|
95
|
+
"description": "",
|
|
96
|
+
"parameters": []
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "MERGE_GITHUB_PULL_REQUEST",
|
|
100
|
+
"description": "",
|
|
101
|
+
"parameters": []
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"name": "PUSH_GITHUB_CODE",
|
|
105
|
+
"description": "",
|
|
106
|
+
"parameters": []
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "REVIEW_GITHUB_PULL_REQUEST",
|
|
110
|
+
"description": "",
|
|
111
|
+
"parameters": []
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
}"""
|
|
115
|
+
_CORE_PROVIDER_DOCS_JSON = """{
|
|
116
|
+
"version": "1.0.0",
|
|
117
|
+
"providers": [
|
|
118
|
+
{
|
|
119
|
+
"name": "GITHUB_ISSUE_CONTEXT",
|
|
120
|
+
"description": "Provides detailed context about a specific GitHub issue or pull request when referenced",
|
|
121
|
+
"dynamic": true
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"name": "GITHUB_REPOSITORY_STATE",
|
|
125
|
+
"description": "Provides context about the current GitHub repository including recent activity",
|
|
126
|
+
"dynamic": true
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
}"""
|
|
130
|
+
_ALL_PROVIDER_DOCS_JSON = """{
|
|
131
|
+
"version": "1.0.0",
|
|
132
|
+
"providers": [
|
|
133
|
+
{
|
|
134
|
+
"name": "GITHUB_ISSUE_CONTEXT",
|
|
135
|
+
"description": "Provides detailed context about a specific GitHub issue or pull request when referenced",
|
|
136
|
+
"dynamic": true
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"name": "GITHUB_REPOSITORY_STATE",
|
|
140
|
+
"description": "Provides context about the current GitHub repository including recent activity",
|
|
141
|
+
"dynamic": true
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
}"""
|
|
145
|
+
_CORE_EVALUATOR_DOCS_JSON = """{
|
|
146
|
+
"version": "1.0.0",
|
|
147
|
+
"evaluators": []
|
|
148
|
+
}"""
|
|
149
|
+
_ALL_EVALUATOR_DOCS_JSON = """{
|
|
150
|
+
"version": "1.0.0",
|
|
151
|
+
"evaluators": []
|
|
152
|
+
}"""
|
|
153
|
+
|
|
154
|
+
core_action_docs: dict[str, object] = json.loads(_CORE_ACTION_DOCS_JSON)
|
|
155
|
+
all_action_docs: dict[str, object] = json.loads(_ALL_ACTION_DOCS_JSON)
|
|
156
|
+
core_provider_docs: dict[str, object] = json.loads(_CORE_PROVIDER_DOCS_JSON)
|
|
157
|
+
all_provider_docs: dict[str, object] = json.loads(_ALL_PROVIDER_DOCS_JSON)
|
|
158
|
+
core_evaluator_docs: dict[str, object] = json.loads(_CORE_EVALUATOR_DOCS_JSON)
|
|
159
|
+
all_evaluator_docs: dict[str, object] = json.loads(_ALL_EVALUATOR_DOCS_JSON)
|
|
160
|
+
|
|
161
|
+
__all__ = [
|
|
162
|
+
"ActionDoc",
|
|
163
|
+
"ProviderDoc",
|
|
164
|
+
"EvaluatorDoc",
|
|
165
|
+
"core_action_docs",
|
|
166
|
+
"all_action_docs",
|
|
167
|
+
"core_provider_docs",
|
|
168
|
+
"all_provider_docs",
|
|
169
|
+
"core_evaluator_docs",
|
|
170
|
+
"all_evaluator_docs",
|
|
171
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from elizaos_plugin_github.providers.issue_context import IssueContextProvider
|
|
2
|
+
from elizaos_plugin_github.providers.repository_state import RepositoryStateProvider
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"RepositoryStateProvider",
|
|
6
|
+
"IssueContextProvider",
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
all_providers = [
|
|
10
|
+
RepositoryStateProvider(),
|
|
11
|
+
IssueContextProvider(),
|
|
12
|
+
]
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Protocol
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ProviderContext(Protocol):
|
|
6
|
+
message: dict[str, object]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def extract_issue_number(text: str) -> int | None:
|
|
10
|
+
patterns = [
|
|
11
|
+
r"#(\d+)",
|
|
12
|
+
r"issue\s*#?(\d+)",
|
|
13
|
+
r"pr\s*#?(\d+)",
|
|
14
|
+
r"pull\s*request\s*#?(\d+)",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
for pattern in patterns:
|
|
18
|
+
match = re.search(pattern, text, re.IGNORECASE)
|
|
19
|
+
if match:
|
|
20
|
+
return int(match.group(1))
|
|
21
|
+
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class IssueContextProvider:
|
|
26
|
+
@property
|
|
27
|
+
def name(self) -> str:
|
|
28
|
+
return "GITHUB_ISSUE_CONTEXT"
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def description(self) -> str:
|
|
32
|
+
return "Provides detailed context about a specific GitHub issue or pull request when referenced"
|
|
33
|
+
|
|
34
|
+
async def get(
|
|
35
|
+
self,
|
|
36
|
+
context: ProviderContext,
|
|
37
|
+
service: object,
|
|
38
|
+
) -> str | None:
|
|
39
|
+
from elizaos_plugin_github.service import GitHubService
|
|
40
|
+
|
|
41
|
+
if not isinstance(service, GitHubService):
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
content = context.message.get("content", {})
|
|
45
|
+
text = ""
|
|
46
|
+
if isinstance(content, dict):
|
|
47
|
+
text = str(content.get("text", ""))
|
|
48
|
+
|
|
49
|
+
issue_number = extract_issue_number(text)
|
|
50
|
+
if not issue_number:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
config = service.config
|
|
55
|
+
|
|
56
|
+
if not config.owner or not config.repo:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
issue = await service.get_issue(config.owner, config.repo, issue_number)
|
|
61
|
+
|
|
62
|
+
if issue.is_pull_request:
|
|
63
|
+
pr = await service.get_pull_request(config.owner, config.repo, issue_number)
|
|
64
|
+
|
|
65
|
+
labels = ", ".join(label.name for label in pr.labels)
|
|
66
|
+
assignees = ", ".join(a.login for a in pr.assignees)
|
|
67
|
+
reviewers = ", ".join(r.login for r in pr.requested_reviewers)
|
|
68
|
+
|
|
69
|
+
parts = [
|
|
70
|
+
f"## Pull Request #{pr.number}: {pr.title}",
|
|
71
|
+
"",
|
|
72
|
+
f"**State:** {pr.state.value}{' (Draft)' if pr.draft else ''}{' (Merged)' if pr.merged else ''}",
|
|
73
|
+
f"**Author:** {pr.user.login}",
|
|
74
|
+
f"**Branch:** {pr.head.ref} → {pr.base.ref}",
|
|
75
|
+
f"**Created:** {pr.created_at}",
|
|
76
|
+
f"**Updated:** {pr.updated_at}",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
if labels:
|
|
80
|
+
parts.append(f"**Labels:** {labels}")
|
|
81
|
+
if assignees:
|
|
82
|
+
parts.append(f"**Assignees:** {assignees}")
|
|
83
|
+
if reviewers:
|
|
84
|
+
parts.append(f"**Reviewers Requested:** {reviewers}")
|
|
85
|
+
|
|
86
|
+
parts.extend(
|
|
87
|
+
[
|
|
88
|
+
"",
|
|
89
|
+
f"**Changes:** +{pr.additions} / -{pr.deletions} ({pr.changed_files} files)",
|
|
90
|
+
"",
|
|
91
|
+
"### Description",
|
|
92
|
+
pr.body or "_No description provided_",
|
|
93
|
+
"",
|
|
94
|
+
f"**URL:** {pr.html_url}",
|
|
95
|
+
]
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return "\n".join(parts)
|
|
99
|
+
|
|
100
|
+
labels = ", ".join(label.name for label in issue.labels)
|
|
101
|
+
assignees = ", ".join(a.login for a in issue.assignees)
|
|
102
|
+
|
|
103
|
+
parts = [
|
|
104
|
+
f"## Issue #{issue.number}: {issue.title}",
|
|
105
|
+
"",
|
|
106
|
+
f"**State:** {issue.state.value}{(' (' + issue.state_reason.value + ')') if issue.state_reason else ''}",
|
|
107
|
+
f"**Author:** {issue.user.login}",
|
|
108
|
+
f"**Created:** {issue.created_at}",
|
|
109
|
+
f"**Updated:** {issue.updated_at}",
|
|
110
|
+
f"**Comments:** {issue.comments}",
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
if labels:
|
|
114
|
+
parts.append(f"**Labels:** {labels}")
|
|
115
|
+
if assignees:
|
|
116
|
+
parts.append(f"**Assignees:** {assignees}")
|
|
117
|
+
if issue.milestone:
|
|
118
|
+
parts.append(f"**Milestone:** {issue.milestone.title}")
|
|
119
|
+
|
|
120
|
+
parts.extend(
|
|
121
|
+
[
|
|
122
|
+
"",
|
|
123
|
+
"### Description",
|
|
124
|
+
issue.body or "_No description provided_",
|
|
125
|
+
"",
|
|
126
|
+
f"**URL:** {issue.html_url}",
|
|
127
|
+
]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return "\n".join(parts)
|
|
131
|
+
|
|
132
|
+
except Exception:
|
|
133
|
+
return f"Issue/PR #{issue_number} not found in {config.owner}/{config.repo}"
|
|
134
|
+
|
|
135
|
+
except Exception as e:
|
|
136
|
+
return f"Unable to fetch issue context: {e}"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from typing import Protocol
|
|
2
|
+
|
|
3
|
+
from elizaos_plugin_github.types import ListIssuesParams, ListPullRequestsParams, RepositoryRef
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ProviderContext(Protocol):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RepositoryStateProvider:
|
|
11
|
+
@property
|
|
12
|
+
def name(self) -> str:
|
|
13
|
+
return "GITHUB_REPOSITORY_STATE"
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def description(self) -> str:
|
|
17
|
+
return "Provides context about the current GitHub repository including recent activity"
|
|
18
|
+
|
|
19
|
+
async def get(
|
|
20
|
+
self,
|
|
21
|
+
_context: ProviderContext,
|
|
22
|
+
service: object,
|
|
23
|
+
) -> str | None:
|
|
24
|
+
from elizaos_plugin_github.service import GitHubService
|
|
25
|
+
|
|
26
|
+
if not isinstance(service, GitHubService):
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
config = service.config
|
|
31
|
+
|
|
32
|
+
if not config.owner or not config.repo:
|
|
33
|
+
return "GitHub repository not configured. Please set GITHUB_OWNER and GITHUB_REPO."
|
|
34
|
+
|
|
35
|
+
repo = await service.get_repository(RepositoryRef(owner=config.owner, repo=config.repo))
|
|
36
|
+
|
|
37
|
+
issues = await service.list_issues(
|
|
38
|
+
ListIssuesParams(
|
|
39
|
+
owner=config.owner,
|
|
40
|
+
repo=config.repo,
|
|
41
|
+
state="open",
|
|
42
|
+
per_page=5,
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Fetch recent open PRs (limit 5)
|
|
47
|
+
pull_requests = await service.list_pull_requests(
|
|
48
|
+
ListPullRequestsParams(
|
|
49
|
+
owner=config.owner,
|
|
50
|
+
repo=config.repo,
|
|
51
|
+
state="open",
|
|
52
|
+
per_page=5,
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
parts: list[str] = [
|
|
57
|
+
f"## GitHub Repository: {repo.full_name}",
|
|
58
|
+
"",
|
|
59
|
+
f"**Description:** {repo.description or 'No description'}",
|
|
60
|
+
f"**Default Branch:** {repo.default_branch}",
|
|
61
|
+
f"**Language:** {repo.language or 'Not specified'}",
|
|
62
|
+
f"**Stars:** {repo.stargazers_count} | **Forks:** {repo.forks_count}",
|
|
63
|
+
f"**Open Issues:** {repo.open_issues_count}",
|
|
64
|
+
"",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
if issues:
|
|
68
|
+
parts.append("### Recent Open Issues")
|
|
69
|
+
for issue in issues:
|
|
70
|
+
labels = ", ".join(label.name for label in issue.labels)
|
|
71
|
+
label_str = f" [{labels}]" if labels else ""
|
|
72
|
+
parts.append(f"- #{issue.number}: {issue.title}{label_str}")
|
|
73
|
+
parts.append("")
|
|
74
|
+
|
|
75
|
+
if pull_requests:
|
|
76
|
+
parts.append("### Recent Open Pull Requests")
|
|
77
|
+
for pr in pull_requests:
|
|
78
|
+
status = "[DRAFT] " if pr.draft else ""
|
|
79
|
+
parts.append(
|
|
80
|
+
f"- #{pr.number}: {status}{pr.title} ({pr.head.ref} → {pr.base.ref})"
|
|
81
|
+
)
|
|
82
|
+
parts.append("")
|
|
83
|
+
|
|
84
|
+
return "\n".join(parts)
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
return f"Unable to fetch GitHub repository state: {e}"
|
|
File without changes
|