Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(github-plugin): add orchestrator action #9

Merged
merged 17 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
githubInteractWithIssuePlugin,
githubInteractWithPRPlugin,
githubModifyIssuePlugin,
githubOrchestratePlugin,
} from "@realityspiral/plugin-github";
import Database from "better-sqlite3";
import yargs from "yargs";
Expand Down Expand Up @@ -559,6 +560,7 @@ export async function createAgent(
githubIdeationPlugin,
githubInteractWithIssuePlugin,
githubInteractWithPRPlugin,
githubOrchestratePlugin,
]
: []),
]
Expand Down
15 changes: 15 additions & 0 deletions plugins/plugin-github/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import {
createPullRequestAction,
githubCreatePullRequestPlugin,
} from "./plugins/createPullRequest";
import {
forkRepositoryAction,
githubForkRepositoryPlugin,
} from "./plugins/forkRepository";
import { githubIdeationPlugin, ideationAction } from "./plugins/ideationPlugin";
import {
githubInitializePlugin,
Expand All @@ -29,6 +33,7 @@ import {
import {
addCommentToPRAction,
closePRAction,
generateCodeFileChangesAction,
githubInteractWithPRPlugin,
implementFeatureAction,
mergePRAction,
Expand All @@ -39,6 +44,10 @@ import {
githubModifyIssuePlugin,
modifyIssueAction,
} from "./plugins/modifyIssue";
import {
githubOrchestratePlugin,
orchestrateAction,
} from "./plugins/orchestrate";
import { documentationFilesProvider } from "./providers/documentationFiles";
import { releasesProvider } from "./providers/releases";
import { sourceCodeProvider } from "./providers/sourceCode";
Expand All @@ -55,6 +64,8 @@ export const plugins = {
githubInteractWithIssuePlugin,
githubInteractWithPRPlugin,
githubIdeationPlugin,
githubOrchestratePlugin,
githubForkRepositoryPlugin,
};

export * from "./plugins/initializeRepository";
Expand All @@ -66,6 +77,7 @@ export * from "./plugins/modifyIssue";
export * from "./plugins/interactWithIssue";
export * from "./plugins/ideationPlugin";
export * from "./plugins/interactWithPR";
export * from "./plugins/orchestrate";

export * from "./providers/sourceCode";
export * from "./providers/testFiles";
Expand Down Expand Up @@ -98,7 +110,10 @@ export const githubPlugin: Plugin = {
reactToIssueAction,
closeIssueAction,
replyToPRCommentAction,
generateCodeFileChangesAction,
implementFeatureAction,
forkRepositoryAction,
orchestrateAction,
],
evaluators: [],
providers: [
Expand Down
5 changes: 3 additions & 2 deletions plugins/plugin-github/src/plugins/createIssue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
type SimilarityIssueCheckContent,
SimilarityIssueCheckSchema,
isCreateIssueContent,
isSimilarityIssueCheckContent,
} from "../types";
import { saveIssueToMemory } from "../utils";
export const createIssueAction: Action = {
Expand Down Expand Up @@ -119,8 +120,8 @@ export const createIssueAction: Action = {
schema: SimilarityIssueCheckSchema,
});

if (!isCreateIssueContent(details.object)) {
elizaLogger.error("Invalid content:", details.object);
if (!isSimilarityIssueCheckContent(similarityCheckDetails.object)) {
elizaLogger.error("Invalid content:", similarityCheckDetails.object);
throw new Error("Invalid content");
}

Expand Down
154 changes: 154 additions & 0 deletions plugins/plugin-github/src/plugins/forkRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import {
type Action,
type HandlerCallback,
type IAgentRuntime,
type Memory,
ModelClass,
type Plugin,
type State,
composeContext,
elizaLogger,
generateObject,
} from "@elizaos/core";
import { GitHubService } from "../services/github";
import { forkRepositoryTemplate } from "../templates";
import {
type ForkRepositoryContent,
ForkRepositorySchema,
isForkRepositoryContent,
} from "../types";

export const forkRepositoryAction: Action = {
name: "FORK_REPOSITORY",
similes: [
"FORK_REPO",
"FORK",
"GITHUB_FORK",
"GITHUB_FORK_REPO",
"GITHUB_FORK_REPOSITORY",
"CREATE_FORK",
],
description: "Forks a GitHub repository",
validate: async (runtime: IAgentRuntime) => {
const token = !!runtime.getSetting("GITHUB_API_TOKEN");
return token;
},
handler: async (
runtime: IAgentRuntime,
message: Memory,
state?: State,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
_options?: any,
callback?: HandlerCallback,
) => {
if (!state) {
// biome-ignore lint/style/noParameterAssign: <explanation>
state = (await runtime.composeState(message)) as State;
} else {
// biome-ignore lint/style/noParameterAssign: <explanation>
state = await runtime.updateRecentMessageState(state);
}

const context = composeContext({
state,
template: forkRepositoryTemplate,
});

const details = await generateObject({
runtime,
context,
modelClass: ModelClass.SMALL,
schema: ForkRepositorySchema,
});

if (!isForkRepositoryContent(details.object)) {
elizaLogger.error("Invalid content:", details.object);
throw new Error("Invalid content");
}

const content = details.object as ForkRepositoryContent;

elizaLogger.info(`Forking repository ${content.owner}/${content.repo}...`);

const githubService = new GitHubService({
owner: content.owner,
repo: content.repo,
auth: runtime.getSetting("GITHUB_API_TOKEN"),
});

try {
const fork = await githubService.forkRepository(
content.owner,
content.repo,
content.organization,
);

elizaLogger.info(`Repository forked successfully! URL: ${fork.html_url}`);

if (callback) {
callback({
text: `Repository forked successfully! URL: ${fork.html_url}`,
action: "FORK_REPOSITORY",
source: "github",
attachments: [],
});
}

return fork;
} catch (error) {
elizaLogger.error(
`Error forking repository ${content.owner}/${content.repo}:`,
error,
);

if (callback) {
callback(
{
text: `Error forking repository ${content.owner}/${content.repo}. Please try again.`,
action: "FORK_REPOSITORY",
source: "github",
},
[],
);
}
}
},
examples: [
[
{
user: "{{user1}}",
content: {
text: "Fork repository octocat/Hello-World",
},
},
{
user: "{{agentName}}",
content: {
text: "Repository forked successfully! URL: https://github.com/user1/Hello-World",
action: "FORK_REPOSITORY",
},
},
],
[
{
user: "{{user1}}",
content: {
text: "Create a fork of repository octocat/Hello-World to my-org organization",
},
},
{
user: "{{agentName}}",
content: {
text: "Repository forked successfully! URL: https://github.com/my-org/Hello-World",
action: "FORK_REPOSITORY",
},
},
],
],
};

export const githubForkRepositoryPlugin: Plugin = {
name: "githubForkRepository",
description: "Integration with GitHub for forking repositories",
actions: [forkRepositoryAction],
};
121 changes: 97 additions & 24 deletions plugins/plugin-github/src/plugins/interactWithPR.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fs from "node:fs/promises";
import {
type Action,
Content,
Expand Down Expand Up @@ -989,6 +990,88 @@ export const replyToPRCommentAction: Action = {
],
};

export const generateCodeFileChangesAction: Action = {
name: "GENERATE_CODE_FILE_CHANGES",
similes: [
"GENERATE_CODE_FILE_CHANGES",
"GENERATE_CODE_CHANGES",
"GENERATE_FILE_CHANGES",
],
description:
"Generates code file changes based on feature requirements or issue details",
validate: async (_runtime: IAgentRuntime) => {
return true; // No specific validation needed
},
handler: async (
runtime: IAgentRuntime,
message: Memory,
state?: State,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
_options?: any,
callback?: HandlerCallback,
) => {
if (!state) {
// biome-ignore lint/style/noParameterAssign: <explanation>
state = (await runtime.composeState(message)) as State;
} else {
// biome-ignore lint/style/noParameterAssign: <explanation>
state = await runtime.updateRecentMessageState(state);
}

const context = composeContext({
state,
template: generateCodeFileChangesTemplate,
});

const details = await generateObject({
runtime,
context,
modelClass: ModelClass.SMALL,
schema: GenerateCodeFileChangesSchema,
});

if (!isGenerateCodeFileChangesContent(details.object)) {
elizaLogger.error("Invalid code file changes content:", details.object);
throw new Error("Invalid code file changes content");
}

const content = details.object as GenerateCodeFileChangesContent;

// write file
await fs.writeFile(
"/tmp/generated-code-file-changes.json",
JSON.stringify(content, null, 2),
);

if (callback) {
await callback({
text: "Generated code file changes successfully!",
action: "GENERATE_CODE_FILE_CHANGES",
attachments: [],
});
}

return content;
},
examples: [
[
{
user: "{{user}}",
content: {
text: "Generate code changes for replacing console.log with elizaLogger.log",
},
},
{
user: "{{agentName}}",
content: {
text: "Generated code file changes successfully!",
action: "GENERATE_CODE_FILE_CHANGES",
},
},
],
],
};

export const implementFeatureAction: Action = {
name: "IMPLEMENT_FEATURE",
similes: ["IMPLEMENT_FEATURE", "REPLACE_LOGS"],
Expand Down Expand Up @@ -1068,33 +1151,22 @@ export const implementFeatureAction: Action = {
}

state.specificIssue = JSON.stringify(issue, null, 2);
// Generate code file changes
const codeFileChangesContext = composeContext({
state,
template: generateCodeFileChangesTemplate,
});

const codeFileChangesDetails = await generateObject({
runtime,
context: codeFileChangesContext,
modelClass: ModelClass.SMALL,
schema: GenerateCodeFileChangesSchema,
});

if (!isGenerateCodeFileChangesContent(codeFileChangesDetails.object)) {
elizaLogger.error(
"Invalid code file changes content:",
codeFileChangesDetails.object,
);
throw new Error("Invalid code file changes content");
}
// Use generateCodeFileChangesAction
message.content.text = `Generate code changes for ${content.feature}`;

const codeFileChangesContent =
codeFileChangesDetails.object as GenerateCodeFileChangesContent;
(await generateCodeFileChangesAction.handler(
runtime,
message,
state,
options,
)) as GenerateCodeFileChangesContent;

state.codeFileChanges = codeFileChangesContent.files;

elizaLogger.info(
"Generated code file changes successfully!",
// write file
await fs.writeFile(
"/tmp/implement-feature-code-file-changes.json",
JSON.stringify(codeFileChangesContent, null, 2),
);

Expand Down Expand Up @@ -1204,6 +1276,7 @@ export const githubInteractWithPRPlugin: Plugin = {
closePRAction,
mergePRAction,
replyToPRCommentAction,
implementFeatureAction,
generateCodeFileChangesAction,
// implementFeatureAction,
],
};
Loading
Loading