From c5b77f32fc1518d5bf5ed525dcc185d429b87c9d Mon Sep 17 00:00:00 2001 From: whilefoo Date: Sun, 3 Nov 2024 22:40:54 +0100 Subject: [PATCH 01/17] feat: initial impl --- bun.lockb | Bin 434517 -> 441700 bytes package.json | 7 +- src/github/github-context.ts | 10 +- src/github/github-event-handler.ts | 8 +- src/github/handlers/help-command.ts | 4 +- src/github/handlers/index.ts | 4 +- src/github/handlers/issue-comment-created.ts | 203 ++++++++++++++++++- src/github/handlers/repository-dispatch.ts | 2 +- src/github/types/env.ts | 2 + src/github/types/plugin-configuration.ts | 2 +- src/github/types/plugin.ts | 17 +- src/github/utils/config.ts | 9 +- src/github/utils/plugins.ts | 2 +- src/sdk/actions.ts | 24 ++- src/sdk/context.ts | 2 + src/sdk/server.ts | 12 +- src/sdk/signature.ts | 3 +- src/types/command.ts | 5 + src/types/manifest.ts | 5 +- src/types/util.ts | 11 + src/worker.ts | 5 + tests/configuration.test.ts | 55 ++--- tests/dispatch.test.ts | 11 +- tests/events.test.ts | 45 +++- tests/main.test.ts | 26 ++- tests/push.test.ts | 17 +- tests/sdk.test.ts | 42 +++- 27 files changed, 433 insertions(+), 100 deletions(-) create mode 100644 src/types/command.ts create mode 100644 src/types/util.ts diff --git a/bun.lockb b/bun.lockb index 2b4d96d40a2aea23fe4f8b7a6d4daf2ecceaeaad..03a434a60399ded7a6334a61ec3e5dc8947021d5 100755 GIT binary patch delta 90312 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 SXx8!?=-ZAR&eye9!u|&vL{cyS delta 85944 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 diff --git a/package.json b/package.json index faa914c..80ccb41 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,9 @@ "typings": "dist/index.d.ts", "exports": { ".": { - "types": "./dist/index.d.ts", - "require": "./dist/index.js", - "import": "./dist/index.mjs" + "types": "./dist/index.d.ts", + "require": "./dist/index.js", + "import": "./dist/index.mjs" } }, "files": [ @@ -65,6 +65,7 @@ "@ubiquity-os/ubiquity-os-logger": "^1.3.2", "dotenv": "16.4.5", "hono": "4.4.13", + "openai": "^4.70.2", "smee-client": "2.0.1", "ts-node": "^10.9.2", "typebox-validators": "0.3.5", diff --git a/src/github/github-context.ts b/src/github/github-context.ts index b71d6db..c5faa25 100644 --- a/src/github/github-context.ts +++ b/src/github/github-context.ts @@ -1,6 +1,7 @@ import { EmitterWebhookEvent as WebhookEvent, EmitterWebhookEventName as WebhookEventName } from "@octokit/webhooks"; import { customOctokit } from "./github-client"; import { GitHubEventHandler } from "./github-event-handler"; +import OpenAI from "openai"; export class GitHubContext { public key: WebhookEventName; @@ -11,8 +12,14 @@ export class GitHubContext; public eventHandler: InstanceType; + public openAi: OpenAI; - constructor(eventHandler: InstanceType, event: WebhookEvent, octokit: InstanceType) { + constructor( + eventHandler: InstanceType, + event: WebhookEvent, + octokit: InstanceType, + openAi: OpenAI + ) { this.eventHandler = eventHandler; this.name = event.name; this.id = event.id; @@ -23,6 +30,7 @@ export class GitHubContext; + openAiClient: OpenAI; }; export class GitHubEventHandler { @@ -24,6 +26,7 @@ export class GitHubEventHandler { private readonly _webhookSecret: string; private readonly _privateKey: string; private readonly _appId: number; + private readonly _openAiClient: OpenAI; constructor(options: Options) { this.environment = options.environment; @@ -31,6 +34,7 @@ export class GitHubEventHandler { this._appId = Number(options.appId); this._webhookSecret = options.webhookSecret; this.pluginChainState = options.pluginChainState; + this._openAiClient = options.openAiClient; this.webhooks = new Webhooks({ secret: this._webhookSecret, @@ -75,10 +79,10 @@ export class GitHubEventHandler { transformEvent(event: EmitterWebhookEvent) { if ("installation" in event.payload && event.payload.installation?.id !== undefined) { const octokit = this.getAuthenticatedOctokit(event.payload.installation.id); - return new GitHubContext(this, event, octokit); + return new GitHubContext(this, event, octokit, this._openAiClient); } else { const octokit = this.getUnauthenticatedOctokit(); - return new GitHubContext(this, event, octokit); + return new GitHubContext(this, event, octokit, this._openAiClient); } } diff --git a/src/github/handlers/help-command.ts b/src/github/handlers/help-command.ts index f5fbeb3..1bea3d3 100644 --- a/src/github/handlers/help-command.ts +++ b/src/github/handlers/help-command.ts @@ -7,8 +7,8 @@ async function parseCommandsFromManifest(context: GitHubContext<"issue_comment.c const commands: string[] = []; const manifest = await getManifest(context, plugin); if (manifest?.commands) { - for (const [key, value] of Object.entries(manifest.commands)) { - commands.push(`| \`/${getContent(key)}\` | ${getContent(value.description)} | \`${getContent(value["ubiquity:example"])}\` |`); + for (const command of manifest.commands) { + commands.push(`| \`/${getContent(command.name)}\` | ${getContent(command.description)} | \`${getContent(command["ubiquity:example"])}\` |`); } } return commands; diff --git a/src/github/handlers/index.ts b/src/github/handlers/index.ts index 21876a0..9dc2131 100644 --- a/src/github/handlers/index.ts +++ b/src/github/handlers/index.ts @@ -28,7 +28,7 @@ export function bindHandlers(eventHandler: GitHubEventHandler) { } export async function shouldSkipPlugin(context: GitHubContext, pluginChain: PluginConfiguration["plugins"][0]) { - if (pluginChain.skipBotEvents && "sender" in context.payload && context.payload.sender?.type === "Bot") { + if (pluginChain.uses[0].skipBotEvents && "sender" in context.payload && context.payload.sender?.type === "Bot") { console.log("Skipping plugin chain because sender is a bot"); return true; } @@ -98,7 +98,7 @@ async function handleEvent(event: EmitterWebhookEvent, eventHandler: InstanceTyp const ref = isGithubPluginObject ? (plugin.ref ?? (await getDefaultBranch(context, plugin.owner, plugin.repo))) : plugin; const token = await eventHandler.getToken(event.payload.installation.id); - const inputs = new PluginInput(context.eventHandler, stateId, context.key, event.payload, settings, token, ref); + const inputs = new PluginInput(context.eventHandler, stateId, context.key, event.payload, settings, token, ref, null); state.inputs[0] = inputs; await eventHandler.pluginChainState.put(stateId, state); diff --git a/src/github/handlers/issue-comment-created.ts b/src/github/handlers/issue-comment-created.ts index e01c493..80ef289 100644 --- a/src/github/handlers/issue-comment-created.ts +++ b/src/github/handlers/issue-comment-created.ts @@ -1,9 +1,208 @@ +import { Manifest } from "../../types/manifest"; import { GitHubContext } from "../github-context"; +import { PluginInput } from "../types/plugin"; +import { isGithubPlugin, PluginConfiguration } from "../types/plugin-configuration"; +import { getConfig } from "../utils/config"; +import { getManifest } from "../utils/plugins"; +import { dispatchWorker, dispatchWorkflow, getDefaultBranch } from "../utils/workflow-dispatch"; import { postHelpCommand } from "./help-command"; export default async function issueCommentCreated(context: GitHubContext<"issue_comment.created">) { - const body = context.payload.comment.body.trim(); - if (/^\/help$/.test(body)) { + const body = context.payload.comment.body.trim().toLowerCase(); + if (body.startsWith(`@ubiquityos`) || body.startsWith(`/`)) { + await commandRouter(context); + } +} + +interface OpenAiFunction { + type: "function"; + function: { + name: string; + description?: string; + parameters?: Record; + strict?: boolean | null; + }; +} + +const embeddedCommands: Array = [ + { + type: "function", + function: { + name: "help", + description: "Shows all available commands and their examples", + strict: false, + parameters: { + type: "object", + properties: {}, + }, + }, + }, + { + type: "function", + function: { + name: "allow", + strict: false, + parameters: { + type: "object", + required: ["username", "label_types"], + properties: { + username: { + type: "string", + description: "the user that will be allowed to change the label", + }, + label_types: { + type: "array", + items: { + enum: ["time", "priority"], + type: "string", + }, + description: "array of label types that user will be allowed to change, it can be empty to remove access from all labels", + }, + }, + additionalProperties: false, + }, + description: "Sets which label types can the user change", + }, + }, +]; + +async function commandRouter(context: GitHubContext<"issue_comment.created">) { + if (!("installation" in context.payload) || context.payload.installation?.id === undefined) { + console.log(`No installation found, cannot invoke command`); + return; + } + + const commands = [...embeddedCommands]; + const config = await getConfig(context); + const pluginsWithManifest: { plugin: PluginConfiguration["plugins"][0]["uses"][0]; manifest: Manifest }[] = []; + for (let i = 0; i < config.plugins.length; ++i) { + const { uses } = config.plugins[i]; + for (let j = 0; j < uses.length; ++j) { + const { plugin } = uses[j]; + const manifest = await getManifest(context, plugin); + if (!manifest?.commands) { + continue; + } + pluginsWithManifest.push({ + plugin: uses[j], + manifest, + }); + for (const command of manifest.commands) { + commands.push({ + type: "function", + function: { + ...command, + strict: true, + }, + }); + } + } + } + + const response = await context.openAi.chat.completions.create({ + model: "gpt-4o-mini", + messages: [ + { + role: "system", + content: [ + { + text: `You are a GitHub bot named **UbiquityOS**. Your role is to interpret and execute commands based on user comments. + +### Instructions: +- **Interpretation Modes**: + 1. **Tagged Natural Language**: The user mentions you with \`@UbiquityOS\`, asking for an action or information. Infer the intended command and parameters. + - Example: \`@UbiquityOS, please allow @user to change priority and time labels.\` + 2. **Direct Command**: The user starts the comment with a command in \`/command\` format. + - Example: \`/allow @user priority time\` + +- **Action**: Map the user's intent to one of your available functions. If no matching function is found, respond that no appropriate command was identified.`, + type: "text", + }, + ], + }, + { + role: "user", + content: [ + { + text: context.payload.comment.body, + type: "text", + }, + ], + }, + ], + temperature: 1, + max_tokens: 2048, + top_p: 1, + frequency_penalty: 0, + presence_penalty: 0, + tools: commands, + parallel_tool_calls: false, + response_format: { + type: "text", + }, + }); + + if (response.choices.length === 0) { + return; + } + + const toolCalls = response.choices[0].message.tool_calls; + if (!toolCalls || toolCalls.length === 0) { + const message = response.choices[0].message.content || "I cannot help you with that."; + await context.octokit.rest.issues.createComment({ + owner: context.payload.repository.owner.login, + repo: context.payload.repository.name, + issue_number: context.payload.issue.number, + body: message, + }); + return; + } + + const toolCall = toolCalls[0]; + if (!toolCall) { + console.log("No tool call"); + return; + } + + const command = { + name: toolCall.function.name, + parameters: toolCall.function.arguments ? JSON.parse(toolCall.function.arguments) : null, + }; + + if (command.name === "help") { await postHelpCommand(context); + return; + } + + const pluginWithManifest = pluginsWithManifest.find((o) => o.manifest?.commands?.some((c) => c.name === command.name)); + if (!pluginWithManifest) { + console.log(`No plugin found for command '${command.name}'`); + return; + } + const { + plugin: { plugin, with: settings }, + } = pluginWithManifest; + + // call plugin + const isGithubPluginObject = isGithubPlugin(plugin); + const stateId = crypto.randomUUID(); + const ref = isGithubPluginObject ? (plugin.ref ?? (await getDefaultBranch(context, plugin.owner, plugin.repo))) : plugin; + const token = await context.eventHandler.getToken(context.payload.installation.id); + const inputs = new PluginInput(context.eventHandler, stateId, context.key, context.payload, settings, token, ref, command); + + try { + if (!isGithubPluginObject) { + await dispatchWorker(plugin, await inputs.getWorkerInputs()); + } else { + await dispatchWorkflow(context, { + owner: plugin.owner, + repository: plugin.repo, + workflowId: plugin.workflowId, + ref: plugin.ref, + inputs: await inputs.getWorkflowInputs(), + }); + } + } catch (e) { + console.error(`An error occurred while processing the plugin chain, will skip plugin ${JSON.stringify(plugin)}`, e); } } diff --git a/src/github/handlers/repository-dispatch.ts b/src/github/handlers/repository-dispatch.ts index 510a8c1..ccd82af 100644 --- a/src/github/handlers/repository-dispatch.ts +++ b/src/github/handlers/repository-dispatch.ts @@ -61,7 +61,7 @@ export async function repositoryDispatch(context: GitHubContext<"repository_disp } else { ref = nextPlugin.plugin; } - const inputs = new PluginInput(context.eventHandler, pluginOutput.state_id, state.eventName, state.eventPayload, settings, token, ref); + const inputs = new PluginInput(context.eventHandler, pluginOutput.state_id, state.eventName, state.eventPayload, settings, token, ref, null); state.currentPlugin++; state.inputs[state.currentPlugin] = inputs; diff --git a/src/github/types/env.ts b/src/github/types/env.ts index 3f73306..dce3b21 100644 --- a/src/github/types/env.ts +++ b/src/github/types/env.ts @@ -5,6 +5,7 @@ export const envSchema = T.Object({ APP_WEBHOOK_SECRET: T.String({ minLength: 1 }), APP_ID: T.String({ minLength: 1 }), APP_PRIVATE_KEY: T.String({ minLength: 1 }), + OPENAI_API_KEY: T.String({ minLength: 1 }), }); export type Env = Static & { @@ -18,6 +19,7 @@ declare global { APP_ID: string; APP_WEBHOOK_SECRET: string; APP_PRIVATE_KEY: string; + OPENAI_API_KEY: string; } } } diff --git a/src/github/types/plugin-configuration.ts b/src/github/types/plugin-configuration.ts index 02316d7..4287767 100644 --- a/src/github/types/plugin-configuration.ts +++ b/src/github/types/plugin-configuration.ts @@ -60,6 +60,7 @@ const pluginChainSchema = T.Array( plugin: githubPluginType(), with: T.Record(T.String(), T.Unknown(), { default: {} }), runsOn: T.Array(emitterType, { default: [] }), + skipBotEvents: T.Boolean({ default: true }), }), { minItems: 1, default: [] } ); @@ -70,7 +71,6 @@ const handlerSchema = T.Array( T.Object({ name: T.Optional(T.String()), uses: pluginChainSchema, - skipBotEvents: T.Boolean({ default: true }), }), { default: [] } ); diff --git a/src/github/types/plugin.ts b/src/github/types/plugin.ts index cac7f2a..ee00902 100644 --- a/src/github/types/plugin.ts +++ b/src/github/types/plugin.ts @@ -2,18 +2,14 @@ import { EmitterWebhookEvent, EmitterWebhookEventName } from "@octokit/webhooks" import { StaticDecode, Type } from "@sinclair/typebox"; import { PluginChain } from "./plugin-configuration"; import { GitHubEventHandler } from "../github-event-handler"; +import { CommandCall } from "../../types/command"; +import { jsonType } from "../../types/util"; export const expressionRegex = /^\s*\${{\s*(\S+)\s*}}\s*$/; -function jsonString() { - return Type.Transform(Type.String()) - .Decode((value) => JSON.parse(value) as Record) - .Encode((value) => JSON.stringify(value)); -} - export const pluginOutputSchema = Type.Object({ state_id: Type.String(), // GitHub forces snake_case - output: jsonString(), + output: jsonType(Type.Record(Type.String(), Type.Unknown())), }); export type PluginOutput = StaticDecode; @@ -26,6 +22,7 @@ export class PluginInput["payload"], settings: unknown, authToken: string, - ref: string + ref: string, + command: CommandCall ) { this.eventHandler = eventHandler; this.stateId = stateId; @@ -43,6 +41,7 @@ export class PluginInput = { eventName: inputs.eventName as TSupportedEvents, - payload: JSON.parse(inputs.eventPayload), + payload: inputs.eventPayload, + command: inputs.command, octokit: new customOctokit({ auth: inputs.authToken }), config: config, env: env, diff --git a/src/sdk/context.ts b/src/sdk/context.ts index 8502b0d..b7e5df7 100644 --- a/src/sdk/context.ts +++ b/src/sdk/context.ts @@ -1,12 +1,14 @@ import { EmitterWebhookEvent as WebhookEvent, EmitterWebhookEventName as WebhookEventName } from "@octokit/webhooks"; import { Logs } from "@ubiquity-os/ubiquity-os-logger"; import { customOctokit } from "./octokit"; +import { CommandCall } from "../types/command"; export interface Context { eventName: TSupportedEvents; payload: { [K in TSupportedEvents]: K extends WebhookEventName ? WebhookEvent : never; }[TSupportedEvents]["payload"]; + command: CommandCall; octokit: InstanceType; config: TConfig; env: TEnv; diff --git a/src/sdk/server.ts b/src/sdk/server.ts index 9c60e6e..441f331 100644 --- a/src/sdk/server.ts +++ b/src/sdk/server.ts @@ -18,12 +18,14 @@ interface Options { postCommentOnError?: boolean; settingsSchema?: TAnySchema; envSchema?: TAnySchema; + disableSignatureVerification?: boolean; // only use for local development } const inputSchema = T.Object({ stateId: T.String(), eventName: T.String(), eventPayload: T.Record(T.String(), T.Any()), + command: T.Union([T.Null(), T.Object({ name: T.String(), parameters: T.Unknown() })]), authToken: T.String(), settings: T.Record(T.String(), T.Any()), ref: T.String(), @@ -41,6 +43,7 @@ export function createPlugin = { eventName: inputs.eventName as TSupportedEvents, payload: inputs.eventPayload, + command: inputs.command, octokit: new customOctokit({ auth: inputs.authToken }), config: config, env: env, diff --git a/src/sdk/signature.ts b/src/sdk/signature.ts index b23f68b..8815fa5 100644 --- a/src/sdk/signature.ts +++ b/src/sdk/signature.ts @@ -5,6 +5,7 @@ interface Inputs { authToken: unknown; settings: unknown; ref: unknown; + command: unknown; } export async function verifySignature(publicKeyPem: string, inputs: Inputs, signature: string) { @@ -16,8 +17,8 @@ export async function verifySignature(publicKeyPem: string, inputs: Inputs, sign settings: inputs.settings, authToken: inputs.authToken, ref: inputs.ref, + command: inputs.command, }; - console.log(JSON.stringify(inputs)); const pemContents = publicKeyPem.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "").trim(); const binaryDer = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0)); diff --git a/src/types/command.ts b/src/types/command.ts new file mode 100644 index 0000000..92ad081 --- /dev/null +++ b/src/types/command.ts @@ -0,0 +1,5 @@ +import { StaticDecode, Type as T } from "@sinclair/typebox"; + +export const commandCallSchema = T.Union([T.Null(), T.Object({ name: T.String(), parameters: T.Unknown() })]); + +export type CommandCall = StaticDecode; diff --git a/src/types/manifest.ts b/src/types/manifest.ts index 28ad2e9..a09485a 100644 --- a/src/types/manifest.ts +++ b/src/types/manifest.ts @@ -5,16 +5,19 @@ import { emitterEventNames } from "@octokit/webhooks"; export const runEvent = T.Union(emitterEventNames.map((o) => T.Literal(o))); export const commandSchema = T.Object({ + name: T.String({ minLength: 1 }), description: T.String({ minLength: 1 }), "ubiquity:example": T.String({ minLength: 1 }), + parameters: T.Optional(T.Record(T.String(), T.Any())), }); export const manifestSchema = T.Object({ name: T.String({ minLength: 1 }), description: T.Optional(T.String({ default: "" })), - commands: T.Optional(T.Record(T.String(), commandSchema, { default: {} })), + commands: T.Optional(T.Array(commandSchema, { default: [] })), "ubiquity:listeners": T.Optional(T.Array(runEvent, { default: [] })), configuration: T.Optional(T.Record(T.String(), T.Any(), { default: {} })), + skipBotEvents: T.Optional(T.Boolean({ default: true })), }); export const manifestValidator = new StandardValidator(manifestSchema); diff --git a/src/types/util.ts b/src/types/util.ts new file mode 100644 index 0000000..8aa6275 --- /dev/null +++ b/src/types/util.ts @@ -0,0 +1,11 @@ +import { Type, TAnySchema } from "@sinclair/typebox"; +import { Value } from "@sinclair/typebox/value"; + +export function jsonType(type: TSchema) { + return Type.Transform(Type.String()) + .Decode((value) => { + const parsed = JSON.parse(value); + return Value.Decode(type, Value.Default(type, parsed)); + }) + .Encode((value) => JSON.stringify(value)); +} diff --git a/src/worker.ts b/src/worker.ts index 00e081c..de266d6 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -5,6 +5,7 @@ import { bindHandlers } from "./github/handlers"; import { Env, envSchema } from "./github/types/env"; import { EmptyStore } from "./github/utils/kv-store"; import { WebhookEventName } from "@octokit/webhooks-types"; +import OpenAI from "openai"; export default { async fetch(request: Request, env: Env): Promise { @@ -13,12 +14,16 @@ export default { const eventName = getEventName(request); const signatureSha256 = getSignature(request); const id = getId(request); + const openAiClient = new OpenAI({ + apiKey: env.OPENAI_API_KEY, + }); const eventHandler = new GitHubEventHandler({ environment: env.ENVIRONMENT, webhookSecret: env.APP_WEBHOOK_SECRET, appId: env.APP_ID, privateKey: env.APP_PRIVATE_KEY, pluginChainState: new EmptyStore(), + openAiClient, }); bindHandlers(eventHandler); await eventHandler.webhooks.verifyAndReceive({ id, name: eventName, payload: await request.text(), signature: signatureSha256 }); diff --git a/tests/configuration.test.ts b/tests/configuration.test.ts index c8666dc..de49c51 100644 --- a/tests/configuration.test.ts +++ b/tests/configuration.test.ts @@ -40,12 +40,14 @@ describe("Configuration tests", () => { data = ` { "name": "plugin", - "commands": { - "command": { + "commands": [ + { + "name": "command", "description": "description", - "ubiquity:example": "example" + "ubiquity:example": "/command" } - } + ], + "skipBotEvents": false } `; } else if (args.path === CONFIG_FULL_PATH) { @@ -54,8 +56,7 @@ describe("Configuration tests", () => { - uses: - plugin: ubiquity/user-activity-watcher:compute.yml@fork/pull/1 with: - settings1: 'enabled' - skipBotEvents: false`; + settings1: 'enabled'`; } else { throw new Error("Not Found"); } @@ -97,12 +98,12 @@ describe("Configuration tests", () => { ref: "fork/pull/1", }, runsOn: [], + skipBotEvents: false, with: { settings1: "enabled", }, }, ], - skipBotEvents: false, }); }); it("Should retrieve the configuration manifest from the proper branch if specified", async () => { @@ -113,27 +114,31 @@ describe("Configuration tests", () => { const content: Record = { withRef: { name: "plugin", - commands: { - command: { + commands: [ + { + name: "command", description: "description", "ubiquity:example": "example", }, - }, + ], configuration: {}, description: "", "ubiquity:listeners": [], + skipBotEvents: true, }, withoutRef: { name: "plugin-no-ref", - commands: { - command: { + commands: [ + { + name: "command", description: "description", "ubiquity:example": "example", }, - }, + ], configuration: {}, description: "", "ubiquity:listeners": [], + skipBotEvents: true, }, }; function getContent({ ref }: Record) { @@ -179,12 +184,14 @@ describe("Configuration tests", () => { data = ` { "name": "plugin", - "commands": { - "command": { + "commands": [ + { + "name": "command", "description": "description", - "ubiquity:example": "example" + "ubiquity:example": "/command" } - } + ], + "skipBotEvents": false } `; } else if (args.path === CONFIG_FULL_PATH) { @@ -193,8 +200,7 @@ describe("Configuration tests", () => { - uses: - plugin: ubiquity/test-plugin with: - settings1: 'enabled' - skipBotEvents: false`; + settings1: 'enabled'`; } else { throw new Error("Not Found"); } @@ -231,7 +237,7 @@ describe("Configuration tests", () => { } as unknown as GitHubContext; const cfg = await getConfig(context); - expect(cfg.plugins[0].skipBotEvents).toEqual(false); + expect(cfg.plugins[0].uses[0].skipBotEvents).toEqual(false); await expect(shouldSkipPlugin(context, cfg.plugins[0])).resolves.toEqual(false); }); it("should return dev config if environment is not production", async () => { @@ -241,12 +247,13 @@ describe("Configuration tests", () => { data = ` { "name": "plugin", - "commands": { - "command": { + "commands": [ + { + "name": "command", "description": "description", - "ubiquity:example": "example" + "ubiquity:example": "/command" } - } + ] } `; } else if (args.path === CONFIG_FULL_PATH) { diff --git a/tests/dispatch.test.ts b/tests/dispatch.test.ts index 38de8fc..ca47952 100644 --- a/tests/dispatch.test.ts +++ b/tests/dispatch.test.ts @@ -65,16 +65,18 @@ describe("handleEvent", () => { HttpResponse.json({ name: "plugin", "ubiquity:listeners": ["issue_comment.created"], - commands: { - foo: { + commands: [ + { + name: "foo", description: "foo command", "ubiquity:example": "/foo bar", }, - bar: { + { + name: "bar", description: "bar command", "ubiquity:example": "/bar foo", }, - }, + ], }) ), http.get("https://api.github.com/repos/test-user/.ubiquity-os/contents/.github%2F.ubiquity-os.config.yml", (req) => { @@ -159,6 +161,7 @@ describe("handleEvent", () => { APP_ID: "1", APP_PRIVATE_KEY: "1234", PLUGIN_CHAIN_STATE: {} as KVNamespace, + OPENAI_API_KEY: "token", }); expect(res).toBeTruthy(); diff --git a/tests/events.test.ts b/tests/events.test.ts index e8fdf6c..b260ab8 100644 --- a/tests/events.test.ts +++ b/tests/events.test.ts @@ -41,16 +41,18 @@ describe("Event related tests", () => { http.get("https://plugin-a.internal/manifest.json", () => HttpResponse.json({ name: "plugin", - commands: { - foo: { + commands: [ + { + name: "foo", description: "foo command", "ubiquity:example": "/foo bar", }, - bar: { + { + name: "bar", description: "bar command", "ubiquity:example": "/bar foo", }, - }, + ], }) ) ); @@ -83,12 +85,13 @@ describe("Event related tests", () => { content: btoa( JSON.stringify({ name: "plugin", - commands: { - action: { + commands: [ + { + name: "action", description: "action", "ubiquity:example": "/action", }, - }, + ], }) ), }, @@ -108,6 +111,31 @@ describe("Event related tests", () => { }, }, }, + openAi: { + chat: { + completions: { + create: function () { + return { + choices: [ + { + message: { + tool_calls: [ + { + type: "function", + function: { + name: "help", + arguments: "", + }, + }, + ], + }, + }, + ], + }; + }, + }, + }, + }, eventHandler: eventHandler, payload: { repository: { @@ -118,6 +146,9 @@ describe("Event related tests", () => { comment: { body: "/help", }, + installation: { + id: 1, + }, } as unknown as GitHubContext<"issue_comment.created">["payload"], } as unknown as GitHubContext); expect(spy).toBeCalledTimes(1); diff --git a/tests/main.test.ts b/tests/main.test.ts index 2f8e94a..63f506b 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -44,16 +44,18 @@ describe("Worker tests", () => { http.get("https://plugin-a.internal/manifest.json", () => HttpResponse.json({ name: "plugin", - commands: { - foo: { + commands: [ + { + name: "foo", description: "foo command", "ubiquity:example": "/foo bar", }, - bar: { + { + name: "bar", description: "bar command", "ubiquity:example": "/bar foo", }, - }, + ], }) ) ); @@ -67,6 +69,7 @@ describe("Worker tests", () => { APP_ID: "", APP_PRIVATE_KEY: "", PLUGIN_CHAIN_STATE: {} as KVNamespace, + OPENAI_API_KEY: "token", }); expect(res.status).toEqual(500); consoleSpy.mockReset(); @@ -144,7 +147,7 @@ describe("Worker tests", () => { const pluginChain = cfg.plugins; expect(pluginChain.length).toBe(1); expect(pluginChain[0].uses.length).toBe(1); - expect(pluginChain[0].skipBotEvents).toBeTruthy(); + expect(pluginChain[0].uses[0].skipBotEvents).toBeTruthy(); expect(pluginChain[0].uses[0].id).toBe("plugin-A"); expect(pluginChain[0].uses[0].plugin).toBe("https://plugin-a.internal"); expect(pluginChain[0].uses[0].with).toEqual({}); @@ -157,12 +160,13 @@ describe("Worker tests", () => { data = ` { "name": "plugin", - "commands": { - "command": { + "commands": [ + { + "name": "command", "description": "description", - "ubiquity:example": "example" + "ubiquity:example": "/command" } - } + ] } `; } else if (args.repo !== ".ubiquity-os") { @@ -232,16 +236,15 @@ describe("Worker tests", () => { workflowId, }, runsOn: [], + skipBotEvents: true, with: { setting1: false, }, }, ], - skipBotEvents: true, }); expect(cfg.plugins.slice(1)).toEqual([ { - skipBotEvents: true, uses: [ { plugin: { @@ -250,6 +253,7 @@ describe("Worker tests", () => { ref: undefined, workflowId: "compute.yml", }, + skipBotEvents: true, runsOn: [], with: { setting2: true, diff --git a/tests/push.test.ts b/tests/push.test.ts index 808cf6e..9515c9a 100644 --- a/tests/push.test.ts +++ b/tests/push.test.ts @@ -41,16 +41,18 @@ describe("Push related tests", () => { http.get("https://plugin-a.internal/manifest.json", () => HttpResponse.json({ name: "plugin", - commands: { - foo: { + commands: [ + { + name: "foo", description: "foo command", "ubiquity:example": "/foo bar", }, - bar: { + { + name: "bar", description: "bar command", "ubiquity:example": "/bar foo", }, - }, + ], }) ) ); @@ -94,12 +96,13 @@ describe("Push related tests", () => { content: btoa( JSON.stringify({ name: "plugin", - commands: { - action: { + commands: [ + { + name: "action", description: "action", "ubiquity:example": "/action", }, - }, + ], configuration: { default: {}, type: "object", diff --git a/tests/sdk.test.ts b/tests/sdk.test.ts index 6657231..a7c0ba3 100644 --- a/tests/sdk.test.ts +++ b/tests/sdk.test.ts @@ -9,6 +9,7 @@ import { GitHubEventHandler } from "../src/github/github-event-handler"; import { EmptyStore } from "../src/github/utils/kv-store"; import { PluginChainState, PluginInput } from "../src/github/types/plugin"; import { EmitterWebhookEventName } from "@octokit/webhooks"; +import OpenAI from "openai"; const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", { modulusLength: 2048, @@ -37,6 +38,7 @@ const eventHandler = new GitHubEventHandler({ appId: "1", privateKey: privateKey, pluginChainState: new EmptyStore(), + openAiClient: undefined as unknown as OpenAI, }); const app = createPlugin( @@ -87,7 +89,16 @@ describe("SDK worker tests", () => { expect(res.status).toEqual(400); }); it("Should deny POST request with invalid signature", async () => { - const inputs = new PluginInput(eventHandler, "stateId", issueCommentedEvent.eventName, issueCommentedEvent.eventPayload, { shouldFail: false }, "test", ""); + const inputs = new PluginInput( + eventHandler, + "stateId", + issueCommentedEvent.eventName, + issueCommentedEvent.eventPayload, + { shouldFail: false }, + "test", + "", + null + ); const res = await app.request("/", { headers: { @@ -129,7 +140,16 @@ describe("SDK worker tests", () => { { kernelPublicKey: publicKey } ); - const inputs = new PluginInput(eventHandler, "stateId", issueCommentedEvent.eventName, issueCommentedEvent.eventPayload, { shouldFail: true }, "test", ""); + const inputs = new PluginInput( + eventHandler, + "stateId", + issueCommentedEvent.eventName, + issueCommentedEvent.eventPayload, + { shouldFail: true }, + "test", + "", + null + ); const res = await app.request("/", { headers: { @@ -154,7 +174,16 @@ describe("SDK worker tests", () => { }); }); it("Should accept correct request", async () => { - const inputs = new PluginInput(eventHandler, "stateId", issueCommentedEvent.eventName, issueCommentedEvent.eventPayload, { shouldFail: false }, "test", ""); + const inputs = new PluginInput( + eventHandler, + "stateId", + issueCommentedEvent.eventName, + issueCommentedEvent.eventPayload, + { shouldFail: false }, + "test", + "", + null + ); const res = await app.request("/", { headers: { @@ -177,7 +206,7 @@ describe("SDK actions tests", () => { }; it("Should accept correct request", async () => { - const inputs = new PluginInput(eventHandler, "stateId", issueCommentedEvent.eventName, issueCommentedEvent.eventPayload, {}, "test_token", ""); + const inputs = new PluginInput(eventHandler, "stateId", issueCommentedEvent.eventName, issueCommentedEvent.eventPayload, {}, "test_token", "", null); const githubInputs = await inputs.getWorkflowInputs(); jest.mock(githubActionImportPath, () => ({ context: { @@ -233,7 +262,7 @@ describe("SDK actions tests", () => { }); }); it("Should deny invalid signature", async () => { - const inputs = new PluginInput(eventHandler, "stateId", issueCommentedEvent.eventName, issueCommentedEvent.eventPayload, {}, "test_token", ""); + const inputs = new PluginInput(eventHandler, "stateId", issueCommentedEvent.eventName, issueCommentedEvent.eventPayload, {}, "test_token", "", null); const githubInputs = await inputs.getWorkflowInputs(); jest.mock("@actions/github", () => ({ @@ -270,7 +299,7 @@ describe("SDK actions tests", () => { expect(setOutput).not.toHaveBeenCalled(); }); it("Should accept inputs in different order", async () => { - const inputs = new PluginInput(eventHandler, "stateId", issueCommentedEvent.eventName, issueCommentedEvent.eventPayload, {}, "test_token", ""); + const inputs = new PluginInput(eventHandler, "stateId", issueCommentedEvent.eventName, issueCommentedEvent.eventPayload, {}, "test_token", "", null); const githubInputs = await inputs.getWorkflowInputs(); jest.mock(githubActionImportPath, () => ({ @@ -285,6 +314,7 @@ describe("SDK actions tests", () => { ref: githubInputs.ref, authToken: githubInputs.authToken, stateId: githubInputs.stateId, + command: githubInputs.command, eventPayload: githubInputs.eventPayload, }, }, From e90a93d4313703b88becd25c2918c154ae0be1fc Mon Sep 17 00:00:00 2001 From: whilefoo Date: Thu, 7 Nov 2024 23:40:53 +0100 Subject: [PATCH 02/17] feat: more context and tests --- src/github/handlers/index.ts | 7 +- src/github/handlers/issue-comment-created.ts | 79 +++-- tests/commands.test.ts | 320 +++++++++++++++++++ tests/events.test.ts | 168 ---------- 4 files changed, 375 insertions(+), 199 deletions(-) create mode 100644 tests/commands.test.ts delete mode 100644 tests/events.test.ts diff --git a/src/github/handlers/index.ts b/src/github/handlers/index.ts index 9dc2131..53eb9d5 100644 --- a/src/github/handlers/index.ts +++ b/src/github/handlers/index.ts @@ -36,9 +36,10 @@ export async function shouldSkipPlugin(context: GitHubContext, pluginChain: Plug if ( context.key === "issue_comment.created" && manifest?.commands && - Object.keys(manifest.commands).length && - !Object.keys(manifest.commands).some( - (command) => "comment" in context.payload && typeof context.payload.comment !== "string" && context.payload.comment?.body.trim().startsWith(`/${command}`) + manifest.commands.length && + !manifest.commands.some( + (command) => + "comment" in context.payload && typeof context.payload.comment !== "string" && context.payload.comment?.body.trim().startsWith(`/${command.name}`) ) ) { console.log(`Skipping plugin chain ${manifest.name} because command does not match.`, manifest.commands); diff --git a/src/github/handlers/issue-comment-created.ts b/src/github/handlers/issue-comment-created.ts index 80ef289..57dc563 100644 --- a/src/github/handlers/issue-comment-created.ts +++ b/src/github/handlers/issue-comment-created.ts @@ -9,9 +9,12 @@ import { postHelpCommand } from "./help-command"; export default async function issueCommentCreated(context: GitHubContext<"issue_comment.created">) { const body = context.payload.comment.body.trim().toLowerCase(); - if (body.startsWith(`@ubiquityos`) || body.startsWith(`/`)) { + if (body.startsWith(`@ubiquityos`)) { await commandRouter(context); } + if (body.startsWith(`/help`)) { + await postHelpCommand(context); + } } interface OpenAiFunction { @@ -76,26 +79,24 @@ async function commandRouter(context: GitHubContext<"issue_comment.created">) { const config = await getConfig(context); const pluginsWithManifest: { plugin: PluginConfiguration["plugins"][0]["uses"][0]; manifest: Manifest }[] = []; for (let i = 0; i < config.plugins.length; ++i) { - const { uses } = config.plugins[i]; - for (let j = 0; j < uses.length; ++j) { - const { plugin } = uses[j]; - const manifest = await getManifest(context, plugin); - if (!manifest?.commands) { - continue; - } - pluginsWithManifest.push({ - plugin: uses[j], - manifest, + const plugin = config.plugins[i].uses[0]; + + const manifest = await getManifest(context, plugin.plugin); + if (!manifest?.commands) { + continue; + } + pluginsWithManifest.push({ + plugin: plugin, + manifest, + }); + for (const command of manifest.commands) { + commands.push({ + type: "function", + function: { + ...command, + strict: true, + }, }); - for (const command of manifest.commands) { - commands.push({ - type: "function", - function: { - ...command, - strict: true, - }, - }); - } } } @@ -106,16 +107,32 @@ async function commandRouter(context: GitHubContext<"issue_comment.created">) { role: "system", content: [ { - text: `You are a GitHub bot named **UbiquityOS**. Your role is to interpret and execute commands based on user comments. + text: ` +You are a GitHub bot named **UbiquityOS**. Your role is to interpret and execute commands based on user comments provided in structured JSON format. + +### JSON Structure: +The input will include the following fields: +- repositoryOwner: The username of the repository owner. +- repositoryName: The name of the repository where the comment was made. +- issueNumber: The issue or pull request number where the comment appears. +- author: The username of the user who posted the comment. +- comment: The comment text directed at UbiquityOS, including the command and any parameters. + +### Example JSON: +{ + "repository_owner": "repoOwnerUsername", + "repository_name": "example-repo", + "issue_number": 42, + "author": "user", + "comment": "@UbiquityOS please allow @user2 to change priority and time labels." +} ### Instructions: -- **Interpretation Modes**: - 1. **Tagged Natural Language**: The user mentions you with \`@UbiquityOS\`, asking for an action or information. Infer the intended command and parameters. - - Example: \`@UbiquityOS, please allow @user to change priority and time labels.\` - 2. **Direct Command**: The user starts the comment with a command in \`/command\` format. - - Example: \`/allow @user priority time\` +- **Interpretation Mode**: + - **Tagged Natural Language**: Interpret the "comment" field provided in JSON. Users will mention you with "@UbiquityOS", followed by their request. Infer the intended command and parameters based on the "comment" content. -- **Action**: Map the user's intent to one of your available functions. If no matching function is found, respond that no appropriate command was identified.`, +- **Action**: Map the user's intent to one of your available functions. When responding, use the "author", "repositoryOwner", "repositoryName", and "issueNumber" fields as context if relevant. If no matching function is found, respond that no appropriate command was identified. +`, type: "text", }, ], @@ -124,7 +141,13 @@ async function commandRouter(context: GitHubContext<"issue_comment.created">) { role: "user", content: [ { - text: context.payload.comment.body, + text: JSON.stringify({ + repositoryOwner: context.payload.repository.owner.login, + repositoryName: context.payload.repository.name, + issueNumber: context.payload.issue.number, + author: context.payload.comment.user?.login, + comment: context.payload.comment.body, + }), type: "text", }, ], diff --git a/tests/commands.test.ts b/tests/commands.test.ts new file mode 100644 index 0000000..060a66b --- /dev/null +++ b/tests/commands.test.ts @@ -0,0 +1,320 @@ +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, jest } from "@jest/globals"; +import { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods"; +import { config } from "dotenv"; +import { http, HttpResponse } from "msw"; +import { GitHubContext } from "../src/github/github-context"; +import { GitHubEventHandler } from "../src/github/github-event-handler"; +import { CONFIG_FULL_PATH } from "../src/github/utils/config"; +import { server } from "./__mocks__/node"; +import "./__mocks__/webhooks"; + +jest.mock("@octokit/plugin-paginate-rest", () => ({})); +jest.mock("@octokit/plugin-rest-endpoint-methods", () => ({})); +jest.mock("@octokit/plugin-retry", () => ({})); +jest.mock("@octokit/plugin-throttling", () => ({})); +jest.mock("@octokit/auth-app", () => ({})); + +config({ path: ".dev.vars" }); + +const name = "ubiquity-os-kernel"; + +const dispatchWorkflow = jest.fn(); +jest.mock("../src/github/utils/workflow-dispatch", () => ({ + //...(jest.requireActual("../src/github/utils/workflow-dispatch") as object), + getDefaultBranch: async () => "main", + dispatchWorkflow: dispatchWorkflow, +})); + +beforeAll(() => { + server.listen(); +}); +afterEach(() => { + server.resetHandlers(); + jest.clearAllMocks(); + jest.resetAllMocks(); +}); +afterAll(() => { + server.close(); +}); + +const eventHandler = { + environment: "production", + getToken: jest.fn().mockReturnValue("1234"), + signPayload: jest.fn().mockReturnValue("sha256=1234"), +} as unknown as GitHubEventHandler; + +function getContent(params?: RestEndpointMethodTypes["repos"]["getContent"]["parameters"]) { + if (params?.path === CONFIG_FULL_PATH) { + return { + data: ` + plugins: + - name: "Run on comment created" + uses: + - id: plugin-A + plugin: https://plugin-a.internal + - name: "Some Action plugin" + uses: + - id: plugin-B + plugin: ubiquity-os/plugin-b + `, + }; + } else if (params?.path === "manifest.json") { + return { + data: { + content: btoa( + JSON.stringify({ + name: "plugin-B", + commands: [ + { + name: "hello", + description: "This command says hello to the username provided in the parameters.", + "ubiquity:example": "/hello @pavlovcik", + parameters: { + type: "object", + properties: { + username: { + type: "string", + description: "the user to say hello to", + }, + }, + }, + }, + ], + }) + ), + }, + }; + } else { + throw new Error("Not found"); + } +} + +const payload = { + repository: { + owner: { login: "ubiquity" }, + name, + }, + issue: { number: 1 }, + installation: { + id: 1, + }, +}; + +describe("Event related tests", () => { + beforeEach(() => { + server.use( + http.get("https://plugin-a.internal/manifest.json", () => + HttpResponse.json({ + name: "plugin-A", + commands: [ + { + name: "foo", + description: "foo command", + "ubiquity:example": "/foo bar", + }, + { + name: "bar", + description: "bar command", + "ubiquity:example": "/bar foo", + }, + ], + }) + ) + ); + }); + + it("Should post the help menu", async () => { + const issues = { + createComment(params?: RestEndpointMethodTypes["issues"]["createComment"]["parameters"]) { + return params; + }, + }; + const spy = jest.spyOn(issues, "createComment"); + + const issueCommentCreated = (await import("../src/github/handlers/issue-comment-created")).default; + await issueCommentCreated({ + id: "", + key: "issue_comment.created", + octokit: { + rest: { + issues, + repos: { + getContent: jest.fn(getContent), + }, + }, + }, + openAi: { + chat: { + completions: { + create: function () { + return { + choices: [ + { + message: { + tool_calls: [ + { + type: "function", + function: { + name: "help", + arguments: "", + }, + }, + ], + }, + }, + ], + }; + }, + }, + }, + }, + eventHandler: eventHandler, + payload: { + ...payload, + comment: { + body: "@UbiquityOS can you tell me all available commands", + }, + } as unknown as GitHubContext<"issue_comment.created">["payload"], + } as unknown as GitHubContext); + expect(spy).toBeCalledTimes(1); + expect(spy.mock.calls).toEqual([ + [ + { + body: + "### Available Commands\n\n\n| Command | Description | Example |\n|---|---|---|\n| `/help` | List" + + " all available commands. | `/help` |\n| `/bar` | bar command | `/bar foo` |\n| `/foo` | foo command | `/foo bar` |\n| `/hello` | This command says hello to the username provided in the parameters. | `/hello @pavlovcik` |", + issue_number: 1, + owner: "ubiquity", + repo: name, + }, + ], + ]); + }); + + it("Should call appropriate plugin", async () => { + const { getDefaultBranch } = await import("../src/github/utils/workflow-dispatch"); + console.log(await getDefaultBranch(undefined as unknown as GitHubContext, "", "")); + + const issues = { + createComment(params?: RestEndpointMethodTypes["issues"]["createComment"]["parameters"]) { + return params; + }, + }; + const spy = jest.spyOn(issues, "createComment"); + + const issueCommentCreated = (await import("../src/github/handlers/issue-comment-created")).default; + await issueCommentCreated({ + id: "", + key: "issue_comment.created", + octokit: { + rest: { + issues, + repos: { + getContent: jest.fn(getContent), + }, + }, + }, + openAi: { + chat: { + completions: { + create: function () { + return { + choices: [ + { + message: { + tool_calls: [ + { + type: "function", + function: { + name: "hello", + arguments: JSON.stringify({ username: "pavlovcik" }), + }, + }, + ], + }, + }, + ], + }; + }, + }, + }, + }, + eventHandler: eventHandler, + payload: { + ...payload, + comment: { + body: "@UbiquityOS can you say hello to @pavlovcik", + }, + } as unknown as GitHubContext<"issue_comment.created">["payload"], + } as unknown as GitHubContext); + expect(spy).toBeCalledTimes(0); + expect(dispatchWorkflow.mock.calls.length).toEqual(1); + expect(dispatchWorkflow.mock.calls[0][1]).toMatchObject({ + owner: "ubiquity-os", + repository: "plugin-b", + ref: "main", + workflowId: "compute.yml", + inputs: { + command: JSON.stringify({ name: "hello", parameters: { username: "pavlovcik" } }), + }, + }); + }); + + it("Should tell the user it cannot help with arbitrary requests", async () => { + const issues = { + createComment(params?: RestEndpointMethodTypes["issues"]["createComment"]["parameters"]) { + return params; + }, + }; + const spy = jest.spyOn(issues, "createComment"); + + const issueCommentCreated = (await import("../src/github/handlers/issue-comment-created")).default; + await issueCommentCreated({ + id: "", + key: "issue_comment.created", + octokit: { + rest: { + issues, + repos: { + getContent: jest.fn(getContent), + }, + }, + }, + openAi: { + chat: { + completions: { + create: function () { + return { + choices: [ + { + message: { + content: "Sorry, but I can't help with that.", + }, + }, + ], + }; + }, + }, + }, + }, + eventHandler: eventHandler, + payload: { + ...payload, + comment: { + body: "@UbiquityOS who is the creator of the universe", + }, + } as unknown as GitHubContext<"issue_comment.created">["payload"], + } as unknown as GitHubContext); + expect(spy).toBeCalledTimes(1); + expect(spy.mock.calls).toEqual([ + [ + { + body: "Sorry, but I can't help with that.", + issue_number: 1, + owner: "ubiquity", + repo: name, + }, + ], + ]); + }); +}); diff --git a/tests/events.test.ts b/tests/events.test.ts deleted file mode 100644 index b260ab8..0000000 --- a/tests/events.test.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, jest } from "@jest/globals"; -import { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods"; -import { config } from "dotenv"; -import { http, HttpResponse } from "msw"; -import { GitHubContext } from "../src/github/github-context"; -import { GitHubEventHandler } from "../src/github/github-event-handler"; -import issueCommentCreated from "../src/github/handlers/issue-comment-created"; -import { CONFIG_FULL_PATH } from "../src/github/utils/config"; -import { server } from "./__mocks__/node"; -import "./__mocks__/webhooks"; - -jest.mock("@octokit/plugin-paginate-rest", () => ({})); -jest.mock("@octokit/plugin-rest-endpoint-methods", () => ({})); -jest.mock("@octokit/plugin-retry", () => ({})); -jest.mock("@octokit/plugin-throttling", () => ({})); -jest.mock("@octokit/auth-app", () => ({})); - -config({ path: ".dev.vars" }); - -const name = "ubiquity-os-kernel"; - -beforeAll(() => { - server.listen(); -}); -afterEach(() => { - server.resetHandlers(); - jest.clearAllMocks(); - jest.resetAllMocks(); -}); -afterAll(() => { - server.close(); -}); - -const eventHandler = { - environment: "production", -} as GitHubEventHandler; - -describe("Event related tests", () => { - beforeEach(() => { - server.use( - http.get("https://plugin-a.internal/manifest.json", () => - HttpResponse.json({ - name: "plugin", - commands: [ - { - name: "foo", - description: "foo command", - "ubiquity:example": "/foo bar", - }, - { - name: "bar", - description: "bar command", - "ubiquity:example": "/bar foo", - }, - ], - }) - ) - ); - }); - it("Should post the help menu when /help command is invoked", async () => { - const issues = { - createComment(params?: RestEndpointMethodTypes["issues"]["createComment"]["parameters"]) { - return params; - }, - }; - const spy = jest.spyOn(issues, "createComment"); - const getContent = jest.fn((params?: RestEndpointMethodTypes["repos"]["getContent"]["parameters"]) => { - if (params?.path === CONFIG_FULL_PATH) { - return { - data: ` - plugins: - - name: "Run on comment created" - uses: - - id: plugin-A - plugin: https://plugin-a.internal - - name: "Some Action plugin" - uses: - - id: plugin-B - plugin: ubiquity-os/plugin-b - `, - }; - } else if (params?.path === "manifest.json") { - return { - data: { - content: btoa( - JSON.stringify({ - name: "plugin", - commands: [ - { - name: "action", - description: "action", - "ubiquity:example": "/action", - }, - ], - }) - ), - }, - }; - } else { - throw new Error("Not found"); - } - }); - await issueCommentCreated({ - id: "", - key: "issue_comment.created", - octokit: { - rest: { - issues, - repos: { - getContent: getContent, - }, - }, - }, - openAi: { - chat: { - completions: { - create: function () { - return { - choices: [ - { - message: { - tool_calls: [ - { - type: "function", - function: { - name: "help", - arguments: "", - }, - }, - ], - }, - }, - ], - }; - }, - }, - }, - }, - eventHandler: eventHandler, - payload: { - repository: { - owner: { login: "ubiquity" }, - name, - }, - issue: { number: 1 }, - comment: { - body: "/help", - }, - installation: { - id: 1, - }, - } as unknown as GitHubContext<"issue_comment.created">["payload"], - } as unknown as GitHubContext); - expect(spy).toBeCalledTimes(1); - expect(spy.mock.calls).toEqual([ - [ - { - body: - "### Available Commands\n\n\n| Command | Description | Example |\n|---|---|---|\n| `/help` | List" + - " all available commands. | `/help` |\n| `/action` | action | `/action` |\n| `/bar` | bar command | `/bar foo` |\n| `/foo` | foo command | `/foo bar` |", - issue_number: 1, - owner: "ubiquity", - repo: name, - }, - ], - ]); - }); -}); From 164f247f16868f99e133ec768d8d4c8d4ef6646b Mon Sep 17 00:00:00 2001 From: whilefoo Date: Sat, 9 Nov 2024 19:50:38 +0100 Subject: [PATCH 03/17] fix: tests --- src/github/handlers/issue-comment-created.ts | 14 +++++++------- tests/commands.test.ts | 15 ++++++--------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/github/handlers/issue-comment-created.ts b/src/github/handlers/issue-comment-created.ts index 57dc563..b582929 100644 --- a/src/github/handlers/issue-comment-created.ts +++ b/src/github/handlers/issue-comment-created.ts @@ -116,14 +116,14 @@ The input will include the following fields: - repositoryName: The name of the repository where the comment was made. - issueNumber: The issue or pull request number where the comment appears. - author: The username of the user who posted the comment. -- comment: The comment text directed at UbiquityOS, including the command and any parameters. +- comment: The comment text directed at UbiquityOS. ### Example JSON: { - "repository_owner": "repoOwnerUsername", - "repository_name": "example-repo", - "issue_number": 42, - "author": "user", + "repositoryOwner": "repoOwnerUsername", + "repositoryName": "example-repo", + "issueNumber": 42, + "author": "user1", "comment": "@UbiquityOS please allow @user2 to change priority and time labels." } @@ -131,7 +131,7 @@ The input will include the following fields: - **Interpretation Mode**: - **Tagged Natural Language**: Interpret the "comment" field provided in JSON. Users will mention you with "@UbiquityOS", followed by their request. Infer the intended command and parameters based on the "comment" content. -- **Action**: Map the user's intent to one of your available functions. When responding, use the "author", "repositoryOwner", "repositoryName", and "issueNumber" fields as context if relevant. If no matching function is found, respond that no appropriate command was identified. +- **Action**: Map the user's intent to one of your available functions. When responding, use the "author", "repositoryOwner", "repositoryName", and "issueNumber" fields as context if relevant. `, type: "text", }, @@ -221,7 +221,7 @@ The input will include the following fields: owner: plugin.owner, repository: plugin.repo, workflowId: plugin.workflowId, - ref: plugin.ref, + ref: ref, inputs: await inputs.getWorkflowInputs(), }); } diff --git a/tests/commands.test.ts b/tests/commands.test.ts index 060a66b..eedc3b3 100644 --- a/tests/commands.test.ts +++ b/tests/commands.test.ts @@ -18,13 +18,6 @@ config({ path: ".dev.vars" }); const name = "ubiquity-os-kernel"; -const dispatchWorkflow = jest.fn(); -jest.mock("../src/github/utils/workflow-dispatch", () => ({ - //...(jest.requireActual("../src/github/utils/workflow-dispatch") as object), - getDefaultBranch: async () => "main", - dispatchWorkflow: dispatchWorkflow, -})); - beforeAll(() => { server.listen(); }); @@ -32,6 +25,7 @@ afterEach(() => { server.resetHandlers(); jest.clearAllMocks(); jest.resetAllMocks(); + jest.resetModules(); }); afterAll(() => { server.close(); @@ -192,8 +186,11 @@ describe("Event related tests", () => { }); it("Should call appropriate plugin", async () => { - const { getDefaultBranch } = await import("../src/github/utils/workflow-dispatch"); - console.log(await getDefaultBranch(undefined as unknown as GitHubContext, "", "")); + const dispatchWorkflow = jest.fn(); + jest.mock("../src/github/utils/workflow-dispatch", () => ({ + getDefaultBranch: jest.fn().mockImplementation(() => Promise.resolve("main")), + dispatchWorkflow: dispatchWorkflow, + })); const issues = { createComment(params?: RestEndpointMethodTypes["issues"]["createComment"]["parameters"]) { From b58f0f0bee7511993d32c2d8e4158852296048bd Mon Sep 17 00:00:00 2001 From: whilefoo Date: Sat, 9 Nov 2024 20:09:43 +0100 Subject: [PATCH 04/17] fix: tests --- tests/dispatch.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/dispatch.test.ts b/tests/dispatch.test.ts index ca47952..2cd8a1b 100644 --- a/tests/dispatch.test.ts +++ b/tests/dispatch.test.ts @@ -38,6 +38,7 @@ jest.mock("../src/github/types/plugin", () => { authToken: this.authToken, ref: this.ref, signature: "", + command: this.command, }; } }, From fcb4078241964c05c6b011bc44fa1415feb5c8b3 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Sat, 9 Nov 2024 20:20:54 +0100 Subject: [PATCH 05/17] fix: remove test command --- src/github/handlers/issue-comment-created.ts | 27 -------------------- 1 file changed, 27 deletions(-) diff --git a/src/github/handlers/issue-comment-created.ts b/src/github/handlers/issue-comment-created.ts index b582929..340f4bc 100644 --- a/src/github/handlers/issue-comment-created.ts +++ b/src/github/handlers/issue-comment-created.ts @@ -40,33 +40,6 @@ const embeddedCommands: Array = [ }, }, }, - { - type: "function", - function: { - name: "allow", - strict: false, - parameters: { - type: "object", - required: ["username", "label_types"], - properties: { - username: { - type: "string", - description: "the user that will be allowed to change the label", - }, - label_types: { - type: "array", - items: { - enum: ["time", "priority"], - type: "string", - }, - description: "array of label types that user will be allowed to change, it can be empty to remove access from all labels", - }, - }, - additionalProperties: false, - }, - description: "Sets which label types can the user change", - }, - }, ]; async function commandRouter(context: GitHubContext<"issue_comment.created">) { From 01854e7d3e1a9ad824b6a34d13e7c2e77b276af4 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Sun, 10 Nov 2024 12:00:45 +0100 Subject: [PATCH 06/17] fix: imports --- bun.lockb | Bin 424833 -> 424833 bytes package.json | 2 +- src/github/handlers/issue-comment-created.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bun.lockb b/bun.lockb index 9f888e1ca53f7b7d5023e9e3f4a357cb23f678e7..16edf4141b7aa7aaba67228901bcb2ec3d74a704 100755 GIT binary patch delta 33 ncmZqtF4_29vY~~sg{g&k3rl}0qhb4mR2Cp+-990e?V~6F;iL@A delta 33 ncmZqtF4_29vY~~sg{g&k3rl}0qe1(GR2Cp+-990e?V~6F;gt-_ diff --git a/package.json b/package.json index 8a4833c..eec39b5 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "@octokit/types": "^13.5.0", "@octokit/webhooks": "13.3.0", "@octokit/webhooks-types": "7.5.1", - "@sinclair/typebox": "^0.33.20", + "@sinclair/typebox": "^0.33.21", "@ubiquity-os/plugin-sdk": "^1.0.11", "dotenv": "16.4.5", "openai": "^4.70.2", diff --git a/src/github/handlers/issue-comment-created.ts b/src/github/handlers/issue-comment-created.ts index 340f4bc..b67ce95 100644 --- a/src/github/handlers/issue-comment-created.ts +++ b/src/github/handlers/issue-comment-created.ts @@ -1,4 +1,4 @@ -import { Manifest } from "../../types/manifest"; +import { Manifest } from "@ubiquity-os/plugin-sdk/manifest"; import { GitHubContext } from "../github-context"; import { PluginInput } from "../types/plugin"; import { isGithubPlugin, PluginConfiguration } from "../types/plugin-configuration"; From 639e4e39f7e45ff449596c1aaea2ad4cb214ef84 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Sun, 10 Nov 2024 23:22:39 +0100 Subject: [PATCH 07/17] feat: additional properties and required --- src/github/handlers/issue-comment-created.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/github/handlers/issue-comment-created.ts b/src/github/handlers/issue-comment-created.ts index b67ce95..e838750 100644 --- a/src/github/handlers/issue-comment-created.ts +++ b/src/github/handlers/issue-comment-created.ts @@ -37,6 +37,7 @@ const embeddedCommands: Array = [ parameters: { type: "object", properties: {}, + additionalProperties: false, }, }, }, @@ -67,6 +68,13 @@ async function commandRouter(context: GitHubContext<"issue_comment.created">) { type: "function", function: { ...command, + parameters: command.parameters + ? { + ...command.parameters, + required: Object.keys(command.parameters.properties), + additionalProperties: false, + } + : undefined, strict: true, }, }); From 6916ad3b31491ef2b8d8531a985727f04bcf67a8 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Wed, 13 Nov 2024 13:12:37 +0100 Subject: [PATCH 08/17] feat: interactive setup --- bun.lockb | Bin 394027 -> 409318 bytes package.json | 11 +- scripts/deploy.ts | 230 ++++++++++++++++++++++ scripts/index.html | 39 ++++ scripts/redirect.html | 34 ++++ {deploy => scripts}/setup-kv-namespace.ts | 0 scripts/setup.ts | 168 ++++++++++++++++ 7 files changed, 480 insertions(+), 2 deletions(-) create mode 100644 scripts/deploy.ts create mode 100644 scripts/index.html create mode 100644 scripts/redirect.html rename {deploy => scripts}/setup-kv-namespace.ts (100%) create mode 100644 scripts/setup.ts diff --git a/bun.lockb b/bun.lockb index 6adfd2700b27b4b8e1e3e11a33d1f6a144679ae0..812bfba099444c430bc0a43167ac3216787461b5 100755 GIT binary patch delta 91242 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

cpc1 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 Gmij+3VCjbd diff --git a/package.json b/package.json index 398cb75..3b33b6e 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "format:prettier": "prettier --write .", "format:cspell": "cspell **/*", "prepare": "node .husky/install.mjs", + "deploy": "tsx ./scripts/deploy.ts", "deploy-dev": "wrangler deploy --env dev", "deploy-production": "wrangler deploy --env production", "worker": "wrangler dev --env dev --port 8787", @@ -30,7 +31,8 @@ "knip-ci": "knip --no-exit-code --reporter json --config .github/knip.ts", "jest:test": "jest --coverage", "plugin:hello-world": "tsx tests/__mocks__/hello-world-plugin.ts", - "setup-kv": "bun --env-file=.dev.vars deploy/setup-kv-namespace.ts" + "setup-kv": "bun --env-file=.dev.vars deploy/setup-kv-namespace.ts", + "setup": "tsx ./scripts/setup.ts" }, "keywords": [ "typescript", @@ -52,7 +54,7 @@ "@octokit/webhooks": "13.3.0", "@octokit/webhooks-types": "7.5.1", "@sinclair/typebox": "^0.33.20", - "@ubiquity-os/plugin-sdk": "^1.0.11", + "@ubiquity-os/plugin-sdk": "^1.0.11", "dotenv": "16.4.5", "typebox-validators": "0.3.5", "yaml": "2.4.5" @@ -65,6 +67,7 @@ "@cspell/dict-software-terms": "3.4.6", "@cspell/dict-typescript": "3.1.5", "@eslint/js": "9.7.0", + "@inquirer/prompts": "^7.1.0", "@jest/globals": "29.7.0", "@mswjs/data": "0.16.1", "@mswjs/http-middleware": "0.10.1", @@ -72,6 +75,7 @@ "@swc/jest": "0.2.36", "@types/jest": "29.5.12", "@types/node": "20.14.10", + "@types/node-rsa": "^1.1.4", "cspell": "8.9.0", "esbuild": "0.23.0", "eslint": "9.7.0", @@ -84,7 +88,10 @@ "jest-junit": "16.0.0", "knip": "5.26.0", "lint-staged": "15.2.7", + "node-rsa": "^1.1.1", "npm-run-all": "4.1.5", + "open": "^10.1.0", + "ora": "^8.1.1", "prettier": "3.3.3", "smee-client": "^2.0.4", "toml": "3.0.0", diff --git a/scripts/deploy.ts b/scripts/deploy.ts new file mode 100644 index 0000000..7d593a6 --- /dev/null +++ b/scripts/deploy.ts @@ -0,0 +1,230 @@ +import { confirm, input, select } from "@inquirer/prompts"; +import { exec, execSync, spawn } from "child_process"; +import { readFileSync, unlinkSync, writeFileSync } from "fs"; +import ora from "ora"; +import path from "path"; +import { parse } from "dotenv"; +import toml from "toml"; +// @ts-expect-error No typings exist for this package +import * as tomlify from "tomlify-j0.4"; + +interface WranglerConfiguration { + name: string; + env: { + [env: string]: { + kv_namespaces?: { + id: string; + binding: string; + }[]; + }; + }; + kv_namespaces: { + id: string; + binding: string; + }[]; +} + +const WRANGLER_PATH = path.resolve(__dirname, "..", "node_modules/.bin/wrangler"); +const WRANGLER_TOML_PATH = path.resolve(__dirname, "..", "wrangler.toml"); +const BINDING_NAME = "PLUGIN_CHAIN_STATE"; + +function checkIfWranglerInstalled() { + return new Promise((resolve) => { + exec(`${WRANGLER_PATH} --version`, (err, stdout, stderr) => { + if (err || stderr) { + resolve(false); + } + resolve(true); + }); + }); +} + +function checkIfWranglerIsLoggedIn() { + return new Promise((resolve, reject) => { + exec(`${WRANGLER_PATH} whoami`, (err, stdout, stderr) => { + if (err) { + reject(err); + } + if (stdout.includes("You are not authenticated") || stderr.includes("You are not authenticated")) { + resolve(false); + } else { + resolve(true); + } + }); + }); +} + +function wranglerLogin() { + return new Promise((resolve, reject) => { + const loginProcess = spawn(WRANGLER_PATH, ["login"], { stdio: "inherit" }); + + loginProcess.on("close", (code) => { + if (code !== 0) { + reject(); + } else { + resolve(); + } + }); + }); +} + +function wranglerBulkSecrets(env: string | null, filePath: string) { + return new Promise((resolve, reject) => { + const args = env ? ["--env", env] : []; + const process = spawn(WRANGLER_PATH, ["secret", "bulk", filePath, ...args], { stdio: "inherit" }); + + process.on("close", (code) => { + if (code !== 0) { + reject(); + } else { + resolve(); + } + }); + process.on("error", (err) => { + reject(err); + }); + }); +} + +function wranglerDeploy(env: string | null) { + return new Promise((resolve, reject) => { + const args = env ? ["--env", env] : []; + const process = spawn(WRANGLER_PATH, ["deploy", ...args], { stdio: "inherit" }); + + process.on("close", (code) => { + if (code !== 0) { + reject(); + } else { + resolve(); + } + }); + }); +} + +function wranglerKvNamespace(projectName: string, namespace: string) { + const kvList = JSON.parse(execSync(`${WRANGLER_PATH} kv namespace list`).toString()) as { id: string; title: string }[]; + const existingNamespace = kvList.find((o) => o.title === namespace || o.title === `${projectName}-${namespace}`); + if (existingNamespace) { + return existingNamespace.id; + } + + const res = execSync(`${WRANGLER_PATH} kv namespace create ${namespace}`).toString(); + + const newId = res.match(/id = \s*"([^"]+)"/)?.[1]; + if (!newId) { + console.log(res); + throw new Error(`The new ID could not be found.`); + } + return newId; +} + +void (async () => { + const spinner = ora("Checking if Wrangler is installed").start(); + const wranglerInstalled = await checkIfWranglerInstalled(); + if (!wranglerInstalled) { + spinner.fail("Wrangler is not installed. Please install it before running this script"); + process.exit(1); + } else { + spinner.succeed("Wrangler is installed"); + } + + spinner.start("Checking if Wrangler is logged in"); + const wranglerLoggedIn = await checkIfWranglerIsLoggedIn(); + if (!wranglerLoggedIn) { + spinner.warn("Wrangler is not logged in. Please login to Wrangler"); + await wranglerLogin(); + spinner.succeed("Wrangler is now logged in"); + } else { + spinner.succeed("Wrangler is logged in"); + } + + spinner.start("Searching environments in wrangler.toml"); + const wranglerToml: WranglerConfiguration = toml.parse(readFileSync(WRANGLER_TOML_PATH, "utf-8")); + if (!wranglerToml) { + spinner.fail("Error parsing wrangler.toml"); + process.exit(1); + } + const envs = Object.keys(wranglerToml.env ?? {}); + let selectedEnv: string | null = null; + if (envs.length === 0) { + spinner.warn("No environments found, choosing default environment"); + } else if (envs.length === 1) { + spinner.warn(`Only one environment found: ${envs[0]}`); + selectedEnv = envs[0]; + } else if (envs.length > 1) { + spinner.stop(); + selectedEnv = await select({ + message: "Select the environment to deploy to:", + choices: envs, + }); + } + + const willSetSecrets = await confirm({ + message: "Do you want to set secrets?", + default: true, + }); + if (willSetSecrets) { + const envFile = await input({ + message: "Enter the name of the env file to use:", + default: `.${selectedEnv}.vars`, + }); + const spinner = ora("Setting secrets").render(); + try { + const env = readFileSync(path.resolve(__dirname, "..", envFile), { encoding: "utf-8" }); + const parsedEnv = parse(env); + if (parsedEnv) { + const tmpPath = path.resolve(__dirname, "..", `${envFile}.json.tmp`); + writeFileSync(tmpPath, JSON.stringify(parsedEnv)); + await wranglerBulkSecrets(selectedEnv, tmpPath); + unlinkSync(tmpPath); // deletes the temporary file + spinner.succeed("Secrets set successfully"); + } + } catch (err) { + spinner.fail(`Error setting secrets: ${err}`); + process.exit(1); + } + } + + spinner.start("Setting up KV namespace"); + try { + const kvNamespace = selectedEnv ? `${selectedEnv}-plugin-chain-state` : `plugin-chain-state`; + const namespaceId = wranglerKvNamespace(wranglerToml.name, kvNamespace); + if (selectedEnv) { + const existingBinding = wranglerToml.env[selectedEnv]?.kv_namespaces?.find((o) => o.binding === BINDING_NAME); + if (!existingBinding) { + wranglerToml.env[selectedEnv] = wranglerToml.env[selectedEnv] ?? {}; + wranglerToml.env[selectedEnv].kv_namespaces = wranglerToml.env[selectedEnv].kv_namespaces ?? []; + wranglerToml.env[selectedEnv].kv_namespaces?.push({ + id: namespaceId, + binding: BINDING_NAME, + }); + } else { + existingBinding.id = namespaceId; + } + } else { + const existingBinding = wranglerToml.kv_namespaces.find((o) => o.binding === BINDING_NAME); + if (!existingBinding) { + wranglerToml.kv_namespaces.push({ + id: namespaceId, + binding: BINDING_NAME, + }); + } else { + existingBinding.id = namespaceId; + } + } + writeFileSync(WRANGLER_TOML_PATH, tomlify.toToml(wranglerToml)); + spinner.succeed(`Using KV namespace ${kvNamespace} with ID: ${namespaceId}`); + } catch (err) { + spinner.fail(`Error setting up KV namespace: ${err}`); + process.exit(1); + } + + spinner.start("Deploying to Cloudflare Workers").stopAndPersist(); + try { + await wranglerDeploy(selectedEnv); + spinner.succeed("Deployed successfully"); + } catch (err) { + spinner.fail(`Error deploying: ${err}`); + process.exit(1); + } +})(); diff --git a/scripts/index.html b/scripts/index.html new file mode 100644 index 0000000..9e7b2d9 --- /dev/null +++ b/scripts/index.html @@ -0,0 +1,39 @@ + + + + + + Register a Github App + + + +

+

Click on the button and follow instructions

+
+
+ +
+
+ + + + diff --git a/scripts/redirect.html b/scripts/redirect.html new file mode 100644 index 0000000..09706f2 --- /dev/null +++ b/scripts/redirect.html @@ -0,0 +1,34 @@ + + + + + + Register a Github App + + + +
+

Successfully created a Github App!

+ +
+ +
+
+ + diff --git a/deploy/setup-kv-namespace.ts b/scripts/setup-kv-namespace.ts similarity index 100% rename from deploy/setup-kv-namespace.ts rename to scripts/setup-kv-namespace.ts diff --git a/scripts/setup.ts b/scripts/setup.ts new file mode 100644 index 0000000..97a971f --- /dev/null +++ b/scripts/setup.ts @@ -0,0 +1,168 @@ +import http from "http"; +import fs from "fs"; +import path from "path"; +import open from "open"; +import ora, { Ora } from "ora"; +import NodeRSA from "node-rsa"; +import { Octokit } from "@octokit/core"; +import { confirm, input } from "@inquirer/prompts"; + +const PORT = 3000; +const DEV_ENV_FILE = ".dev.vars"; + +const manifestTemplate = { + url: "https://github.com/ubiquity-os/ubiquity-os-kernel", + hook_attributes: { + url: "", + }, + redirect_url: `http://localhost:${PORT}/redirect`, + public: true, + default_permissions: { + actions: "write", + issues: "write", + pull_requests: "write", + contents: "write", + members: "read", + }, + default_events: ["issues", "issue_comment", "label", "pull_request", "push", "repository", "repository_dispatch"], +}; + +class GithubAppSetup { + private _octokit: Octokit; + private _server: http.Server; + private _spinner: Ora; + private _url = new URL(`http://localhost:3000`); + private _env = { + ENVIRONMENT: "production", + APP_ID: "", + APP_PRIVATE_KEY: "", + APP_WEBHOOK_SECRET: "", + WEBHOOK_PROXY_URL: `https://smee.io/ubiquityos-kernel-${this.generateRandomString(16)}`, + }; + + constructor() { + this._octokit = new Octokit(); + this._server = http.createServer(this.handleRequest.bind(this)); + this._spinner = ora("Waiting for Github App creation"); + } + + start() { + this._server.listen(this._url.port, () => { + void open(this._url.toString()); + console.log(`If it doesn't open automatically, open this website and follow instructions: ${this._url}`); + this._spinner.start(); + }); + } + + stop() { + this._spinner.stop(); + this._server.close(); + } + + async handleRequest(req: http.IncomingMessage, res: http.ServerResponse) { + try { + const url = new URL(`http://localhost${req.url}`); + if (url.pathname === "/") { + await this.handleIndexRequest(url, req, res); + } else if (url.pathname === "/redirect" && req.method === "GET") { + await this.handleRedirectRequest(url, req, res); + } else { + this.send404Response(res); + } + } catch (error) { + console.error(error); + this.send500Response(res); + } + } + + send404Response(res: http.ServerResponse) { + res.writeHead(404, { "Content-Type": "text/plain" }); + res.end("404 Not Found"); + } + + send500Response(res: http.ServerResponse) { + res.writeHead(500, { "Content-Type": "text/plain" }); + res.end("Server Error"); + } + + sendHtml(res: http.ServerResponse, content: string) { + res.writeHead(500, { "Content-Type": "text/html" }); + res.end(content); + } + + fileExists(file: string) { + try { + fs.accessSync(file); + return true; + } catch (error) { + return false; + } + } + + generateRandomString(length: number) { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let randomString = ""; + for (let i = 0; i < length; i++) { + randomString += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return randomString; + } + + saveEnv(file: string, env: Record) { + const envContent = Object.entries(env) + .map(([key, value]) => `${key}=${value}`) + .join("\n"); + + fs.writeFileSync(path.join(__dirname, "..", file), envContent, { flag: "a" }); + } + + async handleIndexRequest(url: URL, req: http.IncomingMessage, res: http.ServerResponse) { + const manifest = { ...manifestTemplate }; + manifest.hook_attributes.url = this._env.WEBHOOK_PROXY_URL; + + const htmlContent = fs.readFileSync(path.join(__dirname, "index.html")).toString().replace("{{ MANIFEST }}", JSON.stringify(manifest)); + this.sendHtml(res, htmlContent); + } + + async handleRedirectRequest(url: URL, req: http.IncomingMessage, res: http.ServerResponse) { + const code = url.searchParams.get("code"); + if (!code) { + return this.send404Response(res); + } + + const { data } = await this._octokit.request("POST /app-manifests/{code}/conversions", { + code, + }); + + const htmlContent = fs.readFileSync(path.join(__dirname, "redirect.html")).toString().replace("{{ APP_URL }}", data.html_url); + this.sendHtml(res, htmlContent); + + this._server.close(); + this._spinner.succeed("Github App created successfully"); + + // convert from pkcs1 to pkcs8 + const privateKey = new NodeRSA(data.pem, "pkcs1-private-pem"); + const privateKeyPkcs8 = privateKey.exportKey("pkcs8-private-pem").replaceAll("\n", "\\n"); + + this._env.APP_ID = data.id.toString(); + this._env.APP_PRIVATE_KEY = privateKeyPkcs8; + this._env.APP_WEBHOOK_SECRET = data.webhook_secret ?? ""; + + const envFile = await input({ message: "Enter file name to save env:", default: DEV_ENV_FILE }); + if (this.fileExists(envFile) && !(await confirm({ message: "File already exist. Do you want to append to it?", default: false }))) { + return; + } + this.saveEnv(envFile, this._env); + + process.exit(); + } +} + +const setup = new GithubAppSetup(); +setup.start(); + +process.on("SIGINT", () => { + setup.stop(); + console.log("\nProcess interrupted. Exiting gracefully..."); + process.exit(); +}); From 0421c306a0a67ceed8a9a0007b14d93a0649bc14 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Fri, 15 Nov 2024 17:53:32 +0100 Subject: [PATCH 09/17] fix: simplify array check --- src/github/handlers/issue-comment-created.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/github/handlers/issue-comment-created.ts b/src/github/handlers/issue-comment-created.ts index e838750..cc048a6 100644 --- a/src/github/handlers/issue-comment-created.ts +++ b/src/github/handlers/issue-comment-created.ts @@ -151,7 +151,7 @@ The input will include the following fields: } const toolCalls = response.choices[0].message.tool_calls; - if (!toolCalls || toolCalls.length === 0) { + if (!toolCalls?.length) { const message = response.choices[0].message.content || "I cannot help you with that."; await context.octokit.rest.issues.createComment({ owner: context.payload.repository.owner.login, From 67de8cf4dc1ff08597ed02fcd3f00a72505ab204 Mon Sep 17 00:00:00 2001 From: whilefoo Date: Wed, 20 Nov 2024 18:57:09 +0100 Subject: [PATCH 10/17] feat: manifest commands object --- .dev.vars.example | 1 + package.json | 2 +- src/github/handlers/index.ts | 7 +++---- src/github/handlers/issue-comment-created.ts | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.dev.vars.example b/.dev.vars.example index 4278157..3f08e1e 100644 --- a/.dev.vars.example +++ b/.dev.vars.example @@ -2,3 +2,4 @@ WEBHOOK_PROXY_URL=https://smee.io/new APP_WEBHOOK_SECRET=xxxxxx APP_ID=123456 ENVIRONMENT=development | production +OPENAI_API_KEY= diff --git a/package.json b/package.json index eec39b5..3c46884 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "@octokit/types": "^13.5.0", "@octokit/webhooks": "13.3.0", "@octokit/webhooks-types": "7.5.1", - "@sinclair/typebox": "^0.33.21", + "@sinclair/typebox": "0.34.3", "@ubiquity-os/plugin-sdk": "^1.0.11", "dotenv": "16.4.5", "openai": "^4.70.2", diff --git a/src/github/handlers/index.ts b/src/github/handlers/index.ts index 53eb9d5..9dc2131 100644 --- a/src/github/handlers/index.ts +++ b/src/github/handlers/index.ts @@ -36,10 +36,9 @@ export async function shouldSkipPlugin(context: GitHubContext, pluginChain: Plug if ( context.key === "issue_comment.created" && manifest?.commands && - manifest.commands.length && - !manifest.commands.some( - (command) => - "comment" in context.payload && typeof context.payload.comment !== "string" && context.payload.comment?.body.trim().startsWith(`/${command.name}`) + Object.keys(manifest.commands).length && + !Object.keys(manifest.commands).some( + (command) => "comment" in context.payload && typeof context.payload.comment !== "string" && context.payload.comment?.body.trim().startsWith(`/${command}`) ) ) { console.log(`Skipping plugin chain ${manifest.name} because command does not match.`, manifest.commands); diff --git a/src/github/handlers/issue-comment-created.ts b/src/github/handlers/issue-comment-created.ts index cc048a6..4f3ff0f 100644 --- a/src/github/handlers/issue-comment-created.ts +++ b/src/github/handlers/issue-comment-created.ts @@ -63,11 +63,11 @@ async function commandRouter(context: GitHubContext<"issue_comment.created">) { plugin: plugin, manifest, }); - for (const command of manifest.commands) { + for (const [name, command] of Object.entries(manifest.commands)) { commands.push({ type: "function", function: { - ...command, + name: name, parameters: command.parameters ? { ...command.parameters, @@ -178,7 +178,7 @@ The input will include the following fields: return; } - const pluginWithManifest = pluginsWithManifest.find((o) => o.manifest?.commands?.some((c) => c.name === command.name)); + const pluginWithManifest = pluginsWithManifest.find((o) => o.manifest?.commands?.[command.name] !== undefined); if (!pluginWithManifest) { console.log(`No plugin found for command '${command.name}'`); return; From 1fd1fbc33a5982f4dd35877b5aa8a676e95719aa Mon Sep 17 00:00:00 2001 From: whilefoo Date: Mon, 25 Nov 2024 14:03:07 +0100 Subject: [PATCH 11/17] fix: tests --- src/github/handlers/help-command.ts | 4 ++-- tests/commands.test.ts | 26 ++++++++++----------- tests/configuration.test.ts | 35 +++++++++++++---------------- tests/dispatch.test.ts | 10 ++++----- tests/main.test.ts | 17 ++++++-------- tests/push.test.ts | 17 ++++++-------- 6 files changed, 47 insertions(+), 62 deletions(-) diff --git a/src/github/handlers/help-command.ts b/src/github/handlers/help-command.ts index cc1c2dd..86e2e73 100644 --- a/src/github/handlers/help-command.ts +++ b/src/github/handlers/help-command.ts @@ -7,8 +7,8 @@ async function parseCommandsFromManifest(context: GitHubContext<"issue_comment.c const commands: string[] = []; const manifest = await getManifest(context, plugin); if (manifest?.commands) { - for (const command of manifest.commands) { - commands.push(`| \`/${getContent(command.name)}\` | ${getContent(command.description)} | \`${getContent(command["ubiquity:example"])}\` |`); + for (const [name, command] of Object.entries(manifest.commands)) { + commands.push(`| \`/${getContent(name)}\` | ${getContent(command.description)} | \`${getContent(command["ubiquity:example"])}\` |`); } } return commands; diff --git a/tests/commands.test.ts b/tests/commands.test.ts index 42c1851..3f4dec4 100644 --- a/tests/commands.test.ts +++ b/tests/commands.test.ts @@ -17,6 +17,7 @@ jest.mock("@octokit/auth-app", () => ({})); config({ path: ".dev.vars" }); const name = "ubiquity-os-kernel"; +const eventName = "issue_comment.created"; beforeAll(() => { server.listen(); @@ -58,9 +59,8 @@ function getContent(params?: RestEndpointMethodTypes["repos"]["getContent"]["par content: btoa( JSON.stringify({ name: "plugin-B", - commands: [ - { - name: "hello", + commands: { + hello: { description: "This command says hello to the username provided in the parameters.", "ubiquity:example": "/hello @pavlovcik", parameters: { @@ -73,7 +73,7 @@ function getContent(params?: RestEndpointMethodTypes["repos"]["getContent"]["par }, }, }, - ], + }, }) ), }, @@ -100,18 +100,16 @@ describe("Event related tests", () => { http.get("https://plugin-a.internal/manifest.json", () => HttpResponse.json({ name: "plugin-A", - commands: [ - { - name: "foo", + commands: { + foo: { description: "foo command", "ubiquity:example": "/foo bar", }, - { - name: "bar", + bar: { description: "bar command", "ubiquity:example": "/bar foo", }, - ], + }, }) ) ); @@ -128,7 +126,7 @@ describe("Event related tests", () => { const issueCommentCreated = (await import("../src/github/handlers/issue-comment-created")).default; await issueCommentCreated({ id: "", - key: "issue_comment.created", + key: eventName, octokit: { rest: { issues, @@ -202,7 +200,7 @@ describe("Event related tests", () => { const issueCommentCreated = (await import("../src/github/handlers/issue-comment-created")).default; await issueCommentCreated({ id: "", - key: "issue_comment.created", + key: eventName, octokit: { rest: { issues, @@ -268,7 +266,7 @@ describe("Event related tests", () => { const issueCommentCreated = (await import("../src/github/handlers/issue-comment-created")).default; await issueCommentCreated({ id: "", - key: "issue_comment.created", + key: eventName, octokit: { rest: { issues, @@ -350,7 +348,7 @@ describe("Event related tests", () => { const issueCommentCreated = (await import("../src/github/handlers/issue-comment-created")).default; await issueCommentCreated({ id: "", - key: "issue_comment.created", + key: eventName, octokit: { rest: { issues, diff --git a/tests/configuration.test.ts b/tests/configuration.test.ts index de49c51..796dcf3 100644 --- a/tests/configuration.test.ts +++ b/tests/configuration.test.ts @@ -40,13 +40,12 @@ describe("Configuration tests", () => { data = ` { "name": "plugin", - "commands": [ - { - "name": "command", + "commands": { + "command": { "description": "description", "ubiquity:example": "/command" } - ], + }, "skipBotEvents": false } `; @@ -114,13 +113,12 @@ describe("Configuration tests", () => { const content: Record = { withRef: { name: "plugin", - commands: [ - { - name: "command", + commands: { + command: { description: "description", "ubiquity:example": "example", }, - ], + }, configuration: {}, description: "", "ubiquity:listeners": [], @@ -128,13 +126,12 @@ describe("Configuration tests", () => { }, withoutRef: { name: "plugin-no-ref", - commands: [ - { - name: "command", + commands: { + command: { description: "description", "ubiquity:example": "example", }, - ], + }, configuration: {}, description: "", "ubiquity:listeners": [], @@ -184,13 +181,12 @@ describe("Configuration tests", () => { data = ` { "name": "plugin", - "commands": [ - { - "name": "command", + "commands": { + "command": { "description": "description", "ubiquity:example": "/command" } - ], + }, "skipBotEvents": false } `; @@ -247,13 +243,12 @@ describe("Configuration tests", () => { data = ` { "name": "plugin", - "commands": [ - { - "name": "command", + "commands": { + "command": { "description": "description", "ubiquity:example": "/command" } - ] + } } `; } else if (args.path === CONFIG_FULL_PATH) { diff --git a/tests/dispatch.test.ts b/tests/dispatch.test.ts index 2cd8a1b..d6e4b6b 100644 --- a/tests/dispatch.test.ts +++ b/tests/dispatch.test.ts @@ -66,18 +66,16 @@ describe("handleEvent", () => { HttpResponse.json({ name: "plugin", "ubiquity:listeners": ["issue_comment.created"], - commands: [ - { - name: "foo", + commands: { + foo: { description: "foo command", "ubiquity:example": "/foo bar", }, - { - name: "bar", + bar: { description: "bar command", "ubiquity:example": "/bar foo", }, - ], + }, }) ), http.get("https://api.github.com/repos/test-user/.ubiquity-os/contents/.github%2F.ubiquity-os.config.yml", (req) => { diff --git a/tests/main.test.ts b/tests/main.test.ts index 63f506b..f67a759 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -44,18 +44,16 @@ describe("Worker tests", () => { http.get("https://plugin-a.internal/manifest.json", () => HttpResponse.json({ name: "plugin", - commands: [ - { - name: "foo", + commands: { + foo: { description: "foo command", "ubiquity:example": "/foo bar", }, - { - name: "bar", + bar: { description: "bar command", "ubiquity:example": "/bar foo", }, - ], + }, }) ) ); @@ -160,13 +158,12 @@ describe("Worker tests", () => { data = ` { "name": "plugin", - "commands": [ - { - "name": "command", + "commands": { + "command": { "description": "description", "ubiquity:example": "/command" } - ] + } } `; } else if (args.repo !== ".ubiquity-os") { diff --git a/tests/push.test.ts b/tests/push.test.ts index 9515c9a..808cf6e 100644 --- a/tests/push.test.ts +++ b/tests/push.test.ts @@ -41,18 +41,16 @@ describe("Push related tests", () => { http.get("https://plugin-a.internal/manifest.json", () => HttpResponse.json({ name: "plugin", - commands: [ - { - name: "foo", + commands: { + foo: { description: "foo command", "ubiquity:example": "/foo bar", }, - { - name: "bar", + bar: { description: "bar command", "ubiquity:example": "/bar foo", }, - ], + }, }) ) ); @@ -96,13 +94,12 @@ describe("Push related tests", () => { content: btoa( JSON.stringify({ name: "plugin", - commands: [ - { - name: "action", + commands: { + action: { description: "action", "ubiquity:example": "/action", }, - ], + }, configuration: { default: {}, type: "object", From 1c293d8057bfe13282b7d8a6ec2998b43f6ae995 Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Tue, 26 Nov 2024 15:55:23 +0900 Subject: [PATCH 12/17] fix(workflow): update worker-deploy and setup-kv scripts Update to use oven-sh/setup-bun@v2 and correct the script path in package.json. --- .github/workflows/worker-deploy.yml | 7 +------ package.json | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/worker-deploy.yml b/.github/workflows/worker-deploy.yml index 1616e5b..3ce42f4 100644 --- a/.github/workflows/worker-deploy.yml +++ b/.github/workflows/worker-deploy.yml @@ -17,12 +17,7 @@ jobs: - name: Check out repository uses: actions/checkout@v4 - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 20.10.0 - - - uses: oven-sh/setup-bun@v1 + - uses: oven-sh/setup-bun@v2 - name: Run setup script run: | diff --git a/package.json b/package.json index 86a7d0c..e805beb 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "knip-ci": "knip --no-exit-code --reporter json --config .github/knip.ts", "jest:test": "jest --coverage", "plugin:hello-world": "tsx tests/__mocks__/hello-world-plugin.ts", - "setup-kv": "bun --env-file=.dev.vars deploy/setup-kv-namespace.ts", + "setup-kv": "bun --env-file=.dev.vars scripts/setup-kv-namespace.ts", "setup": "tsx ./scripts/setup.ts" }, "keywords": [ From 7fb513ea6104906333dbcc0021c40088686dd0f0 Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Tue, 26 Nov 2024 15:57:36 +0900 Subject: [PATCH 13/17] chore: update cspell word list Added new words to the cspell configuration for enhanced spell-check. --- .cspell.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.cspell.json b/.cspell.json index b0c80a1..ce50f46 100644 --- a/.cspell.json +++ b/.cspell.json @@ -4,7 +4,8 @@ "ignorePaths": ["**/*.json", "**/*.css", "node_modules", "**/*.log"], "useGitignore": true, "language": "en", - "words": ["dataurl", "devpool", "fkey", "mswjs", "outdir", "servedir", "supabase", "typebox", "ubiquity-os", "smee", "tomlify", "hono", "cfworker"], + "words": ["dataurl", "devpool", "fkey", "mswjs", "outdir", "servedir", "supabase", "typebox", "ubiquity-os", + "smee", "tomlify", "hono", "cfworker", "pavlovcik", "ghijklmnopqrstuvwxyz", "GHIJKLMNOPQRSTUVWXYZ", "ubiquityos"], "dictionaries": ["typescript", "node", "software-terms"], "import": ["@cspell/dict-typescript/cspell-ext.json", "@cspell/dict-node/cspell-ext.json", "@cspell/dict-software-terms"], "ignoreRegExpList": ["[0-9a-fA-F]{6}"] From 96aef6ece4cd8b9cd1ccdaf369c372c6aa8c46bc Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Tue, 26 Nov 2024 16:06:51 +0900 Subject: [PATCH 14/17] chore: add OPENAI_API_KEY to worker deploy workflow Include OPENAI_API_KEY in environment and secrets for deployments. --- .github/workflows/worker-deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/worker-deploy.yml b/.github/workflows/worker-deploy.yml index 3ce42f4..e297dd4 100644 --- a/.github/workflows/worker-deploy.yml +++ b/.github/workflows/worker-deploy.yml @@ -51,11 +51,13 @@ jobs: APP_PRIVATE_KEY APP_WEBHOOK_SECRET ENVIRONMENT + OPENAI_API_KEY env: APP_WEBHOOK_SECRET: ${{ secrets.APP_WEBHOOK_SECRET }} APP_ID: ${{ secrets.APP_ID }} APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} ENVIRONMENT: ${{ secrets.ENVIRONMENT }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - name: Write Deployment URL to Summary run: | From 9a658c9e1baf6f4360cf3ff3b6b85d501170bd7f Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Fri, 29 Nov 2024 17:21:04 +0900 Subject: [PATCH 15/17] chore: added context wait until to async operation --- src/worker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/worker.ts b/src/worker.ts index de266d6..711c0b9 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -8,7 +8,7 @@ import { WebhookEventName } from "@octokit/webhooks-types"; import OpenAI from "openai"; export default { - async fetch(request: Request, env: Env): Promise { + async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { try { validateEnv(env); const eventName = getEventName(request); @@ -26,7 +26,7 @@ export default { openAiClient, }); bindHandlers(eventHandler); - await eventHandler.webhooks.verifyAndReceive({ id, name: eventName, payload: await request.text(), signature: signatureSha256 }); + ctx.waitUntil(eventHandler.webhooks.verifyAndReceive({ id, name: eventName, payload: await request.text(), signature: signatureSha256 })); return new Response("ok\n", { status: 200, headers: { "content-type": "text/plain" } }); } catch (error) { return handleUncaughtError(error); From 73ea3fc05347e4354a0afb08f2e126520d857e19 Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Fri, 29 Nov 2024 18:36:02 +0900 Subject: [PATCH 16/17] chore: fixed test --- tests/dispatch.test.ts | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/tests/dispatch.test.ts b/tests/dispatch.test.ts index d6e4b6b..58ac6a1 100644 --- a/tests/dispatch.test.ts +++ b/tests/dispatch.test.ts @@ -154,18 +154,40 @@ describe("handleEvent", () => { }); const worker = (await import("../src/worker")).default; - const res = await worker.fetch(req, { - ENVIRONMENT: "production", - APP_WEBHOOK_SECRET: secret, - APP_ID: "1", - APP_PRIVATE_KEY: "1234", - PLUGIN_CHAIN_STATE: {} as KVNamespace, - OPENAI_API_KEY: "token", + // I didn't find a better option to make sure that the non-awaited ctx.waitUntil is resolved + // eslint-disable-next-line no-async-promise-executor + const waitUntilPromise = new Promise(async (resolve) => { + const res = await worker.fetch( + req, + { + ENVIRONMENT: "production", + APP_WEBHOOK_SECRET: secret, + APP_ID: "1", + APP_PRIVATE_KEY: "1234", + PLUGIN_CHAIN_STATE: {} as KVNamespace, + OPENAI_API_KEY: "token", + }, + { + waitUntil(promise: Promise) { + promise + .then(() => { + resolve(); + }) + .catch(() => { + resolve(); + }); + }, + passThroughOnException() {}, + } + ); + + expect(res).toBeTruthy(); }); - expect(res).toBeTruthy(); - // 2 calls means the execution didn't break + await waitUntilPromise; + expect(dispatchWorker).toHaveBeenCalledTimes(2); + dispatchWorker.mockReset(); }); }); From 72d1a60f5c914b70cd55644bb5c1ac48f71da21e Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Fri, 29 Nov 2024 18:48:59 +0900 Subject: [PATCH 17/17] chore: fixed test --- tests/main.test.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tests/main.test.ts b/tests/main.test.ts index f67a759..e8c782d 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -61,14 +61,21 @@ describe("Worker tests", () => { it("Should fail on missing env variables", async () => { const req = new Request("http://localhost:8080"); const consoleSpy = jest.spyOn(console, "error").mockImplementation(() => jest.fn()); - const res = await worker.fetch(req, { - ENVIRONMENT: "production", - APP_WEBHOOK_SECRET: "", - APP_ID: "", - APP_PRIVATE_KEY: "", - PLUGIN_CHAIN_STATE: {} as KVNamespace, - OPENAI_API_KEY: "token", - }); + const res = await worker.fetch( + req, + { + ENVIRONMENT: "production", + APP_WEBHOOK_SECRET: "", + APP_ID: "", + APP_PRIVATE_KEY: "", + PLUGIN_CHAIN_STATE: {} as KVNamespace, + OPENAI_API_KEY: "token", + }, + { + waitUntil: function () {}, + passThroughOnException: function () {}, + } + ); expect(res.status).toEqual(500); consoleSpy.mockReset(); });