elizaos_plugin_github/actions/
push_code.rs1#![allow(missing_docs)]
2
3use async_trait::async_trait;
4use serde_json::json;
5
6use super::{ActionContext, ActionResult, GitHubAction};
7use crate::error::Result;
8use crate::types::{CreateCommitParams, FileChange};
9use crate::GitHubService;
10
11pub struct PushCodeAction;
12
13#[async_trait]
14impl GitHubAction for PushCodeAction {
15 fn name(&self) -> &str {
16 "PUSH_GITHUB_CODE"
17 }
18
19 fn description(&self) -> &str {
20 "Creates a commit with file changes and pushes to a GitHub branch."
21 }
22
23 fn similes(&self) -> Vec<&str> {
24 vec![
25 "COMMIT_CODE",
26 "PUSH_CHANGES",
27 "COMMIT_FILES",
28 "PUSH_FILES",
29 "GIT_PUSH",
30 "SAVE_CODE",
31 ]
32 }
33
34 async fn validate(&self, context: &ActionContext) -> Result<bool> {
35 let text = context
36 .message
37 .get("content")
38 .and_then(|c| c.get("text"))
39 .and_then(|t| t.as_str())
40 .unwrap_or("")
41 .to_lowercase();
42
43 Ok(text.contains("push")
44 || text.contains("commit")
45 || text.contains("save")
46 || text.contains("upload"))
47 }
48
49 async fn handler(
50 &self,
51 context: &ActionContext,
52 service: &GitHubService,
53 ) -> Result<ActionResult> {
54 let text = context
55 .message
56 .get("content")
57 .and_then(|c| c.get("text"))
58 .and_then(|t| t.as_str())
59 .unwrap_or("");
60
61 let message = context
62 .state
63 .get("message")
64 .and_then(|m| m.as_str())
65 .map(|s| s.to_string())
66 .unwrap_or_else(|| text.chars().take(100).collect());
67
68 let branch = context
69 .state
70 .get("branch")
71 .and_then(|b| b.as_str())
72 .map(|s| s.to_string())
73 .unwrap_or_else(|| "main".to_string());
74
75 let files: Vec<FileChange> = context
76 .state
77 .get("files")
78 .and_then(|f| serde_json::from_value(f.clone()).ok())
79 .unwrap_or_default();
80
81 if files.is_empty() {
82 return Ok(ActionResult::error("No files to commit"));
83 }
84
85 let author_name = context
86 .state
87 .get("authorName")
88 .and_then(|a| a.as_str())
89 .map(|s| s.to_string());
90
91 let author_email = context
92 .state
93 .get("authorEmail")
94 .and_then(|a| a.as_str())
95 .map(|s| s.to_string());
96
97 let params = CreateCommitParams {
98 owner: context.owner.clone(),
99 repo: context.repo.clone(),
100 message,
101 files,
102 branch,
103 parent_sha: None,
104 author_name,
105 author_email,
106 };
107
108 let commit = service.create_commit(params).await?;
109 let short_sha = commit.sha.chars().take(7).collect::<String>();
110
111 Ok(ActionResult::success(
112 format!("Pushed commit {} to {}", short_sha, &commit.html_url),
113 json!({
114 "sha": commit.sha,
115 "html_url": commit.html_url,
116 "message": commit.message,
117 }),
118 ))
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use serde_json::json;
126
127 #[tokio::test]
128 async fn test_validate_with_push_keyword() {
129 let action = PushCodeAction;
130 let context = ActionContext {
131 message: json!({
132 "content": { "text": "Push the changes to the repository" }
133 }),
134 owner: "test".to_string(),
135 repo: "test".to_string(),
136 state: json!({}),
137 };
138
139 assert!(action.validate(&context).await.unwrap());
140 }
141
142 #[tokio::test]
143 async fn test_validate_with_commit_keyword() {
144 let action = PushCodeAction;
145 let context = ActionContext {
146 message: json!({
147 "content": { "text": "Commit these files to the repository" }
148 }),
149 owner: "test".to_string(),
150 repo: "test".to_string(),
151 state: json!({}),
152 };
153
154 assert!(action.validate(&context).await.unwrap());
155 }
156
157 #[tokio::test]
158 async fn test_validate_without_keywords() {
159 let action = PushCodeAction;
160 let context = ActionContext {
161 message: json!({
162 "content": { "text": "Hello world" }
163 }),
164 owner: "test".to_string(),
165 repo: "test".to_string(),
166 state: json!({}),
167 };
168
169 assert!(!action.validate(&context).await.unwrap());
170 }
171
172 #[test]
173 fn test_action_properties() {
174 let action = PushCodeAction;
175 assert_eq!(action.name(), "PUSH_GITHUB_CODE");
176 assert!(!action.description().is_empty());
177 assert!(action.similes().contains(&"COMMIT_CODE"));
178 assert!(action.similes().contains(&"PUSH_CHANGES"));
179 }
180}