Skip to content

Commit

Permalink
docs(examples): user confirmation (#111)
Browse files Browse the repository at this point in the history
<!--
This pull request template provides suggested sections for framing your
work.
You're welcome to change or remove headers if it doesn't fit your use
case. :)
-->

### What are you trying to accomplish?
Another example to add from the discussion in #83 

### What approach did you choose and why?
Adding a full example how to create the user confirmation and handling
the response, for easier exploration.

### What should reviewers focus on?
If things fit the right way, happy to adjust when needed 😄
  • Loading branch information
rajbos authored Oct 2, 2024
1 parent ab12443 commit 3743211
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 0 deletions.
146 changes: 146 additions & 0 deletions examples/prompt-with-user-confirmation/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { createServer } from "node:http";
import { Octokit } from "octokit";
import {
createAckEvent,
createDoneEvent,
prompt,
verifyAndParseRequest,
createConfirmationEvent,
getUserConfirmation,
createTextEvent,
} from "@copilot-extensions/preview-sdk";

// Define the port to listen on
const PORT = 3000;

// Define the handler function
async function handler(request, response) {
console.log(`Received [${request.method}] to [${request.url}]`);

if (request.method !== "POST") {
// Handle other request methods if necessary
response.writeHead(405, { "Content-Type": "text/plain" });
console.log(`Method ${request.method} not allowed`);

response.end("Method Not Allowed");
return;
}

// get a token to use
const tokenForUser = request.headers["x-github-token"];

// get the user information with the token
const octokit = new Octokit({ auth: tokenForUser });
const user = await octokit.request("GET /user");

// Collect incoming data chunks to use in the `on("end")` event
const body = await getBody(request);
const signature = String(request.headers["github-public-key-signature"]);
const keyID = String(request.headers["github-public-key-identifier"]);

try {
const { isValidRequest, payload } = await verifyAndParseRequest(
body,
signature,
keyID,
{
token: tokenForUser,
},
);

if (!isValidRequest) {
console.error("Request verification failed");
response.writeHead(401, { "Content-Type": "text/plain" });
response.end("Request could not be verified");
return;
}

// write the acknowledge event to let Copilot know we are handling the request
// this will also show the message "Copilot is responding" in the chat
response.write(createAckEvent());

// check if we have user confirmation input in the payload
// this will not be true the first time, only after the user has confirmed
const userConfirmation = getUserConfirmation(payload);

if (userConfirmation) {
console.log("Received a user confirmation", userConfirmation);
// handle the confirmation response here and close the conversation
if (userConfirmation.accepted) {
response.write(createTextEvent("Thank you for confirming you like oranges!"));
} else {
response.write(createTextEvent("Thank you for letting me know you don't like oranges!"));
}
// tell Copilot we are done handling the request
response.end(createDoneEvent());
console.log("Response sent");
// break the code flow here with early exit
return;
} else {
// The user's last response was not a confirmation, so we can continue the flow below
}

// pick up the user prompt and handle it
const payload_message = payload.messages[payload.messages.length - 1];
if (payload_message.content.includes("orange")) {
console.log("The user said orange, so we trigger the confirmation event");

response.write(
createConfirmationEvent({
id: "123",
title: "Please confirm?",
message: "So you like oranges?",
}),
);
}
else {
console.log("Calling the GitHub Copilot API with the user prompt");
// forward the prompt to the Copilot API is the last message in the payload
const { stream } = await prompt.stream(payload_message.content, {
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
messages: payload.messages, // we are giving the prompt the existing messages in this chat conversation for context
token: tokenForUser,
});

// stream the prompt response back to Copilot
for await (const chunk of stream) {
response.write(new TextDecoder().decode(chunk));
}
}

// write the done event to let Copilot know we are done handling the request
response.end(createDoneEvent());
console.log("Response sent");
} catch (error) {
console.error("Error:", error);
response.writeHead(500, { "Content-Type": "text/plain" });
response.end("Internal Server Error");
}
}

// Create an HTTP server
const server = createServer(handler);

// Start the server
server.listen(PORT);
console.log(`Server started at http://localhost:${PORT}`);

/**
*
* @param {import("node:http").IncomingMessage} request
* @returns
*/
function getBody(request) {
return new Promise((resolve) => {
const bodyParts = [];
let body;
request
.on("data", (chunk) => {
bodyParts.push(chunk);
})
.on("end", () => {
body = Buffer.concat(bodyParts).toString();
resolve(body);
});
});
}
12 changes: 12 additions & 0 deletions examples/prompt-with-user-confirmation/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"private": true,
"type": "module",
"scripts": {
"start": "node index.js",
"watch": "node --watch index.js"
},
"dependencies": {
"@copilot-extensions/preview-sdk": "../../",
"octokit": "^4.0.2"
}
}

0 comments on commit 3743211

Please sign in to comment.