Skip to content

Commit dc86e4c

Browse files
authored
Merge branch 'main' into francisfuzz/79-security
2 parents 141ba2d + f61a49d commit dc86e4c

File tree

8 files changed

+202
-60
lines changed

8 files changed

+202
-60
lines changed

.github/pull_request_template.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!--
2+
This pull request template provides suggested sections for framing your work.
3+
You're welcome to change or remove headers if it doesn't fit your use case. :)
4+
-->
5+
6+
### What are you trying to accomplish?
7+
8+
### What approach did you choose and why?
9+
10+
### What should reviewers focus on?

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
node_modules
21
coverage
2+
examples/**/package-lock.json
3+
node_modules

examples/README.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Examples
2+
3+
This folder contains examples to show how you can use the Copilot Agent SDK in your own projects.
4+
5+
If you find any issues or have any questions, please let us know by opening an issue, pull requests welcome (please read the (contribution guidelines)[../CONTRIBUTING.md] first).
6+
7+
## Running the examples
8+
9+
These examples are setup so that you can run them inside a GitHub Codespace. To do so, follow these steps:
10+
11+
1. Open the GitHub Codespace by clicking the "Code" button in the top right of the repository and selecting "Open with Codespaces".
12+
2. Once the Codespace is open, you can run the examples by running the following command in the terminal:
13+
14+
```sh
15+
# go to the correct folder
16+
cd examples/<example-name>
17+
# install dependencies
18+
npm install
19+
# run the example
20+
npm run start
21+
```
22+
23+
> [!IMPORTANT]
24+
> By default, the examples will run on port 3000 and ports in Codespaces are private by default. If you want to access the examples from your browser, you'll need to make the port public. To do so, click on the "Ports" tab in the Codespace and right click, Go to "Port Visibility" next to port 3000 and set it to `public`. Do be aware that this will make the port accessible to anyone with the link to your Codespace. The port visibility setting is not saved when the Codespace is stopped, so you'll need to set it again if you restart the Codespace or node. The url does stay the same as long as the Codespace is not deleted.
25+
26+
> [!TIP]
27+
> Don't forget to set the Codespace url (with the port) in your GitHub App that you are using. The Copilot backend needs to be able to reach your Codespace to send messages to your extension. If your app is not running, the port is not public, or the codespace is stopped, the extension will not work.

examples/hello-world/index.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { createServer } from "node:http";
2+
3+
import {
4+
createTextEvent,
5+
createDoneEvent,
6+
} from "@copilot-extensions/preview-sdk";
7+
8+
const server = createServer((request, ressponse) => {
9+
console.log(`Received [${request.method}] to [${request.url}]`);
10+
11+
if (request.method === "GET") {
12+
return ressponse.end("ok");
13+
}
14+
15+
ressponse.write(createTextEvent("Hello, world!"));
16+
ressponse.end(createDoneEvent());
17+
});
18+
19+
const PORT = process.env.PORT || 3000;
20+
server.listen(PORT, () => {
21+
console.log(`Server is running on port ${PORT}`);
22+
});

examples/hello-world/package.json

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"private": true,
3+
"type": "module",
4+
"scripts": {
5+
"start": "node index.js",
6+
"watch": "node --watch index.js"
7+
},
8+
"dependencies": {
9+
"@copilot-extensions/preview-sdk": "../../"
10+
}
11+
}

examples/prompt/index.js

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { createServer } from "node:http";
2+
import { Octokit } from "octokit";
3+
import {
4+
createAckEvent,
5+
createDoneEvent,
6+
prompt,
7+
verifyAndParseRequest,
8+
} from "@copilot-extensions/preview-sdk";
9+
10+
// Define the port to listen on
11+
const PORT = 3000;
12+
13+
// Define the handler function
14+
async function handler(request, response) {
15+
console.log(`Received [${request.method}] to [${request.url}]`);
16+
17+
if (request.method !== "POST") {
18+
// Handle other request methods if necessary
19+
response.writeHead(405, { "Content-Type": "text/plain" });
20+
console.log(`Method ${request.method} not allowed`);
21+
22+
response.end("Method Not Allowed");
23+
return;
24+
}
25+
26+
// get a token to use
27+
const tokenForUser = request.headers["x-github-token"];
28+
29+
// get the user information with the token
30+
const octokit = new Octokit({ auth: tokenForUser });
31+
const user = await octokit.request("GET /user");
32+
33+
// Collect incoming data chunks to use in the `on("end")` event
34+
const body = await getBody(request);
35+
const signature = String(request.headers["github-public-key-signature"]);
36+
const keyID = String(request.headers["github-public-key-identifier"]);
37+
38+
try {
39+
const { isValidRequest, payload } = await verifyAndParseRequest(
40+
body,
41+
signature,
42+
keyID,
43+
{
44+
token: tokenForUser,
45+
},
46+
);
47+
48+
if (!isValidRequest) {
49+
console.error("Request verification failed");
50+
response.writeHead(401, { "Content-Type": "text/plain" });
51+
response.end("Request could not be verified");
52+
return;
53+
}
54+
55+
// write the acknowledge event to let Copilot know we are handling the request
56+
// this will also show the message "Copilot is responding" in the chat
57+
response.write(createAckEvent());
58+
59+
console.log("Calling the GitHub Copilot API with the user prompt");
60+
// the prompt to forward to the Copilot API is the last message in the payload
61+
const payload_message = payload.messages[payload.messages.length - 1];
62+
const { stream } = await prompt.stream(payload_message.content, {
63+
system: `You are a helpful assistant that replies to user messages as if you were the Blackbeard Pirate. Start every response with the user's name, which is @${user.data.login}`, // extra instructions for the prompt
64+
messages: payload.messages, // we are giving the prompt the existing messages in this chat conversation for context
65+
token: tokenForUser,
66+
});
67+
68+
// stream the prompt response back to Copilot
69+
for await (const chunk of stream) {
70+
response.write(new TextDecoder().decode(chunk));
71+
}
72+
73+
// write the done event to let Copilot know we are done handling the request
74+
response.end(createDoneEvent());
75+
console.log("Response sent");
76+
} catch (error) {
77+
console.error("Error:", error);
78+
response.writeHead(500, { "Content-Type": "text/plain" });
79+
response.end("Internal Server Error");
80+
}
81+
}
82+
83+
// Create an HTTP server
84+
const server = createServer(handler);
85+
86+
// Start the server
87+
server.listen(PORT);
88+
console.log(`Server started at http://localhost:${PORT}`);
89+
90+
/**
91+
*
92+
* @param {import("node:http").IncomingMessage} request
93+
* @returns
94+
*/
95+
function getBody(request) {
96+
return new Promise((resolve) => {
97+
const bodyParts = [];
98+
let body;
99+
request
100+
.on("data", (chunk) => {
101+
bodyParts.push(chunk);
102+
})
103+
.on("end", () => {
104+
body = Buffer.concat(bodyParts).toString();
105+
resolve(body);
106+
});
107+
});
108+
}

examples/prompt/package.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"private": true,
3+
"type": "module",
4+
"scripts": {
5+
"start": "node index.js",
6+
"watch": "node --watch index.js"
7+
},
8+
"dependencies": {
9+
"@copilot-extensions/preview-sdk": "../../",
10+
"octokit": "^4.0.2"
11+
}
12+
}

0 commit comments

Comments
 (0)