diff --git a/package-lock.json b/package-lock.json index bea1784..5a3c103 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@copilot-extensions/preview-sdk": "^1.0.0", + "@copilot-extensions/preview-sdk": "^2.6.1", "express": "^4.19.2", "openai": "^4.55.0" }, @@ -21,9 +21,9 @@ } }, "node_modules/@copilot-extensions/preview-sdk": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@copilot-extensions/preview-sdk/-/preview-sdk-1.0.0.tgz", - "integrity": "sha512-q0cfLN0BBj0vmaj2vi1hgKgscV/StwdhV2NlqH1Vvargl9AEL/CyPILrIgUHOdARYCQ+vFBbLJjlnzK6C1krAQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@copilot-extensions/preview-sdk/-/preview-sdk-2.6.1.tgz", + "integrity": "sha512-TZiNVnguyC9PDoDKivvsNphOVQwhrV39QgdRoO68wHQn8uSK/0+aB9ElcFJRxrEJKYp6OgYyJS3XIlUTJ8SKJw==", "license": "MIT", "dependencies": { "@octokit/request": "^9.1.3", diff --git a/package.json b/package.json index f44da3c..2cdfc2f 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "license": "ISC", "description": "", "dependencies": { - "@copilot-extensions/preview-sdk": "^1.0.0", + "@copilot-extensions/preview-sdk": "^2.6.1", "express": "^4.19.2", "openai": "^4.55.0" }, @@ -22,4 +22,4 @@ "tsx": "^4.18.0", "typescript": "^5.5.4" } -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 6c36240..3a3e2a4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,19 +1,53 @@ -import express from "express"; +import { createServer, IncomingMessage } from "node:http"; + +import { verifyAndParseRequest } from "@copilot-extensions/preview-sdk"; import OpenAI from "openai"; -import { verifySignatureMiddleware } from "./validate-signature.js"; + import { describeModel } from "./functions/describe-model.js"; import { executeModel } from "./functions/execute-model.js"; import { listModels } from "./functions/list-models.js"; import { RunnerResponse } from "./functions.js"; import { recommendModel } from "./functions/recommend-model.js"; import { ModelsAPI } from "./models-api.js"; -const app = express(); -app.post("/", verifySignatureMiddleware, express.json(), async (req, res) => { +const server = createServer(async (request, response) => { + if (request.method === "GET") { + response.statusCode = 200; + response.end(`OK`); + return; + } + + const body = await getBody(request); + + let verifyAndParseRequestResult: Awaited>; + const apiKey = request.headers["x-github-token"] as string; + try { + const signature = request.headers["github-public-key-signature"] as string; + const keyID = request.headers["github-public-key-identifier"] as string; + verifyAndParseRequestResult = await verifyAndParseRequest(body, signature, keyID, { + token: apiKey, + }); + } catch (err) { + console.error(err); + response.statusCode = 401 + response.end("Unauthorized"); + return + } + + const { isValidRequest, payload } = verifyAndParseRequestResult + + if (!isValidRequest) { + console.log("Signature verification failed"); + response.statusCode = 401 + response.end("Unauthorized"); + } + + console.log("Signature verified"); + // Use the GitHub API token sent in the request - const apiKey = req.get("X-GitHub-Token"); if (!apiKey) { - res.status(400).end(); + response.statusCode = 400 + response.end() return; } @@ -50,8 +84,8 @@ app.post("/", verifySignatureMiddleware, express.json(), async (req, res) => { "<-- END OF LIST OF MODELS -->", ].join("\n"), }, - ...req.body.messages, - ].concat(req.body.messages); + ...payload.messages, + ].concat(payload.messages); console.time("tool-call"); const toolCaller = await capiClient.chat.completions.create({ @@ -74,15 +108,16 @@ app.post("/", verifySignatureMiddleware, express.json(), async (req, res) => { const stream = await capiClient.chat.completions.create({ stream: true, model: "gpt-4", - messages: req.body.messages, + // @ts-expect-error - TODO @gr2m - type incompatibility between @openai/api and @copilot-extensions/preview-sdk + messages: payload.messages, }); for await (const chunk of stream) { const chunkStr = "data: " + JSON.stringify(chunk) + "\n\n"; - res.write(chunkStr); + response.write(chunkStr); } - res.write("data: [DONE]\n\n"); - res.end(); + response.write("data: [DONE]\n\n"); + response.end(); return; } @@ -102,10 +137,12 @@ app.post("/", verifySignatureMiddleware, express.json(), async (req, res) => { console.log("\t with args", args); const func = new funcClass(modelsAPI); - functionCallRes = await func.execute(req.body.messages, args); + // @ts-expect-error - TODO @gr2m - type incompatibility between @openai/api and @copilot-extensions/preview-sdk + functionCallRes = await func.execute(payload.messages, args); } catch (err) { console.error(err); - res.status(500).end(); + response.statusCode = 500 + response.end(); return; } console.timeEnd("function-exec"); @@ -123,23 +160,33 @@ app.post("/", verifySignatureMiddleware, express.json(), async (req, res) => { console.time("streaming"); for await (const chunk of stream) { const chunkStr = "data: " + JSON.stringify(chunk) + "\n\n"; - res.write(chunkStr); + response.write(chunkStr); } - res.write("data: [DONE]\n\n"); + response.write("data: [DONE]\n\n"); console.timeEnd("streaming"); - res.end(); + response.end(); } catch (err) { console.error(err); - res.status(500).end(); + response.statusCode = 500 + response.end() } }); -// Health check -app.get("/", (req, res) => { - res.send("OK"); -}); +const port = process.env.PORT || "3000" +server.listen(port); +console.log(`Server running at http://localhost:${port}`); -const port = Number(process.env.PORT || "3000"); -app.listen(port, () => { - console.log(`Server is running on http://localhost:${port}`); -}); +function getBody(request: IncomingMessage): Promise { + return new Promise((resolve) => { + const bodyParts: any[] = []; + let body; + request + .on("data", (chunk) => { + bodyParts.push(chunk); + }) + .on("end", () => { + body = Buffer.concat(bodyParts).toString(); + resolve(body); + }); + }); +} diff --git a/src/validate-signature.ts b/src/validate-signature.ts deleted file mode 100644 index c24c20a..0000000 --- a/src/validate-signature.ts +++ /dev/null @@ -1,31 +0,0 @@ -import express, { type Request, Response, NextFunction } from "express"; -import { verify } from "@copilot-extensions/preview-sdk"; - -export async function verifySignatureMiddleware( - req: Request, - res: Response, - next: NextFunction -) { - express.text({ type: "*/*" })(req, res, async () => { - try { - const signature = req.get("GitHub-Public-Key-Signature") as string; - const keyID = req.get("GitHub-Public-Key-Identifier") as string; - const tokenForUser = req.get("X-GitHub-Token") as string; - const verified = await verify(req.body, signature, keyID, { - token: tokenForUser, - }); - if (!verified) { - console.log("Signature verification failed"); - return res.status(401).send("Unauthorized"); - } - - console.log("Signature verified"); - - req.body = JSON.parse(req.body.toString("utf-8")); - next(); - } catch (err) { - console.error(err); - res.status(401).send("Unauthorized"); - } - }); -}