elizaos_plugin_github/providers/
repository_state.rs1#![allow(missing_docs)]
2
3use serde_json::json;
4
5use super::{GitHubProvider, ProviderContext, ProviderResult};
6use crate::error::Result;
7use crate::types::{ListIssuesParams, ListPullRequestsParams};
8use crate::GitHubService;
9
10pub struct RepositoryStateProvider;
13
14impl GitHubProvider for RepositoryStateProvider {
15 fn name(&self) -> &str {
16 "REPOSITORY_STATE"
17 }
18
19 fn description(&self) -> &str {
20 "Provides context about the current GitHub repository including recent activity"
21 }
22
23 async fn get(
24 &self,
25 _context: &ProviderContext,
26 service: &GitHubService,
27 ) -> Result<ProviderResult> {
28 let config = service.config();
29 let owner = config.owner.as_deref().unwrap_or("");
30 let repo = config.repo.as_deref().unwrap_or("");
31
32 if owner.is_empty() || repo.is_empty() {
33 return Ok(ProviderResult {
34 context: "GitHub repository not configured. Please set GITHUB_OWNER and GITHUB_REPO.".to_string(),
35 data: json!({}),
36 });
37 }
38
39 let repository = service.get_repository(owner, repo).await?;
40
41 let issues = service
43 .list_issues(ListIssuesParams {
44 owner: owner.to_string(),
45 repo: repo.to_string(),
46 state: crate::types::IssueStateFilter::Open,
47 labels: None,
48 sort: crate::types::IssueSort::Created,
49 direction: crate::types::SortDirection::Desc,
50 assignee: None,
51 creator: None,
52 per_page: 5,
53 page: 1,
54 })
55 .await?;
56
57 let pull_requests = service
59 .list_pull_requests(ListPullRequestsParams {
60 owner: owner.to_string(),
61 repo: repo.to_string(),
62 state: crate::types::PullRequestStateFilter::Open,
63 head: None,
64 base: None,
65 sort: crate::types::PullRequestSort::Created,
66 direction: crate::types::SortDirection::Desc,
67 per_page: 5,
68 page: 1,
69 })
70 .await?;
71
72 let mut parts = vec![
73 format!("## GitHub Repository: {}", repository.full_name),
74 String::new(),
75 format!(
76 "**Description:** {}",
77 repository
78 .description
79 .as_deref()
80 .unwrap_or("No description")
81 ),
82 format!("**Default Branch:** {}", repository.default_branch),
83 format!(
84 "**Language:** {}",
85 repository.language.as_deref().unwrap_or("Not specified")
86 ),
87 format!(
88 "**Stars:** {} | **Forks:** {}",
89 repository.stargazers_count, repository.forks_count
90 ),
91 format!("**Open Issues:** {}", repository.open_issues_count),
92 String::new(),
93 ];
94
95 if !issues.is_empty() {
96 parts.push("### Recent Open Issues".to_string());
97 for issue in &issues {
98 let labels: String = issue
99 .labels
100 .iter()
101 .map(|l| l.name.as_str())
102 .collect::<Vec<_>>()
103 .join(", ");
104 let label_str = if labels.is_empty() {
105 String::new()
106 } else {
107 format!(" [{}]", labels)
108 };
109 parts.push(format!(
110 "- #{}: {}{}",
111 issue.number, issue.title, label_str
112 ));
113 }
114 parts.push(String::new());
115 }
116
117 if !pull_requests.is_empty() {
118 parts.push("### Recent Open Pull Requests".to_string());
119 for pr in &pull_requests {
120 let status = if pr.draft { "[DRAFT] " } else { "" };
121 parts.push(format!(
122 "- #{}: {}{} ({} → {})",
123 pr.number, status, pr.title, pr.head.branch_ref, pr.base.branch_ref
124 ));
125 }
126 parts.push(String::new());
127 }
128
129 let context_str = parts.join("\n");
130
131 Ok(ProviderResult {
132 context: context_str,
133 data: json!({
134 "name": repository.name,
135 "full_name": repository.full_name,
136 "default_branch": repository.default_branch,
137 "language": repository.language,
138 "stars": repository.stargazers_count,
139 "forks": repository.forks_count,
140 "open_issues": repository.open_issues_count,
141 "recent_issues": issues.iter().map(|i| json!({
142 "number": i.number,
143 "title": i.title,
144 })).collect::<Vec<_>>(),
145 "recent_prs": pull_requests.iter().map(|pr| json!({
146 "number": pr.number,
147 "title": pr.title,
148 "draft": pr.draft,
149 })).collect::<Vec<_>>(),
150 }),
151 })
152 }
153}
154
155pub struct GitHubRepositoryStateProvider;
157
158impl GitHubProvider for GitHubRepositoryStateProvider {
159 fn name(&self) -> &str {
160 "GITHUB_REPOSITORY_STATE"
161 }
162
163 fn description(&self) -> &str {
164 "Provides context about the current GitHub repository including recent activity"
165 }
166
167 fn get(
168 &self,
169 context: &ProviderContext,
170 service: &GitHubService,
171 ) -> impl std::future::Future<Output = Result<ProviderResult>> + Send {
172 RepositoryStateProvider.get(context, service)
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn test_provider_names() {
182 let provider = RepositoryStateProvider;
183 assert_eq!(provider.name(), "REPOSITORY_STATE");
184 assert!(!provider.description().is_empty());
185 assert!(
186 provider.description().contains("repository"),
187 "Description should mention repository"
188 );
189
190 let alias = GitHubRepositoryStateProvider;
191 assert_eq!(alias.name(), "GITHUB_REPOSITORY_STATE");
192 }
193}