Skip to main content

elizaos_plugin_github/providers/
repository_state.rs

1#![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
10/// Provides context about the current GitHub repository including
11/// recent issues and pull requests for agent conversations.
12pub 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        // Fetch recent open issues (limit 5)
42        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        // Fetch recent open PRs (limit 5)
58        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
155/// TS-parity alias provider (name: `GITHUB_REPOSITORY_STATE`).
156pub 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}