Skip to content

Commit 2e4c32f

Browse files
authoredSep 1, 2024
feat: parseRequestBody(), transformPayloadForOpenAICompatibility(), verifyAndParseRequest(), getUserMessage(), getUserConfirmation() (#34)
1 parent 2c0e04f commit 2e4c32f

8 files changed

+448
-14
lines changed
 

‎README.md

+84-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ export default handler(request, response) {
4242

4343
### Verification
4444

45-
#### `async verifyRequestByKeyId(rawBody, signature, keyId, options)`
45+
<a name=verifyRequestByKeyId></a>
46+
47+
#### `async verifyRequestByKeyId(rawBody, signature, keyId, requestOptions)`
4648

4749
Verify the request payload using the provided signature and key ID. The method will request the public key from GitHub's API for the given keyId and then verify the payload.
4850

@@ -202,6 +204,87 @@ import { createDoneEvent } from "@copilot-extensions/preview-sdk";
202204
response.write(createDoneEvent().toString());
203205
```
204206
207+
### Parsing
208+
209+
<a name="parseRequestBody"></a>
210+
211+
#### `parseRequestBody(body)`
212+
213+
Parses the raw request body and returns an object with type support.
214+
215+
⚠️ **It's well possible that the type is not 100% correct. Please send pull requests to `index.d.ts` to improve it**
216+
217+
```js
218+
import { parseRequestBody } from "@copilot-extensions/preview-sdk";
219+
220+
const payload = parseRequestBody(rawBody);
221+
// When your IDE supports types, typing "payload." should prompt the available keys and their types.
222+
```
223+
224+
#### `transformPayloadForOpenAICompatibility()`
225+
226+
For cases when you want to pipe a user request directly to OpenAI, use this method to remove Copilot-specific fields from the request payload.
227+
228+
```js
229+
import { transformPayloadForOpenAICompatibility } from "@copilot-extensions/preview-sdk";
230+
import { OpenAI } from "openai";
231+
232+
const openaiPayload = transformPayloadForOpenAICompatibility(payload);
233+
234+
const openai = new OpenAI({ apiKey: OPENAI_API_KEY });
235+
const stream = openai.beta.chat.completions.stream({
236+
...openaiPayload,
237+
model: "gpt-4-1106-preview",
238+
stream: true,
239+
});
240+
```
241+
242+
#### `verifyAndParseRequest()`
243+
244+
Convenience method to verify and parse a request in one go. It calls [`verifyRequestByKeyId()`](#verifyRequestByKeyId) and [`parseRequestBody()`](#parseRequestBody) internally.
245+
246+
```js
247+
import { verifyAndParseRequest } from "@copilot-extensions/preview-sdk";
248+
249+
const { isValidRequest, payload } = await verifyAndParseRequest(
250+
request,
251+
signature,
252+
key
253+
);
254+
255+
if (!isValidRequest) {
256+
throw new Error("Request could not be verified");
257+
}
258+
259+
// `payload` has type support.
260+
```
261+
262+
#### `getUserMessage()`
263+
264+
Convencience method to get the user's message from the request payload.
265+
266+
```js
267+
import { getUserMessage } from "@copilot-extensions/preview-sdk";
268+
269+
const userMessage = getUserMessage(payload);
270+
```
271+
272+
#### `getUserConfirmation()`
273+
274+
Convencience method to get the user's confirmation from the request payload (in case the user's last response was a confirmation).
275+
276+
```js
277+
import { getUserConfirmation } from "@copilot-extensions/preview-sdk";
278+
279+
const userConfirmation = getUserConfirmation(payload);
280+
281+
if (userConfirmation) {
282+
console.log("Received a user confirmation", userConfirmation);
283+
} else {
284+
// The user's last response was not a confirmation
285+
}
286+
```
287+
205288
## Dreamcode
206289
207290
While implementing the lower-level functionality, we also dream big: what would our dream SDK for Coplitot extensions look like? Please have a look and share your thoughts and ideas:

‎index.d.ts

+126
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { request } from "@octokit/request";
22

3+
// verification types
4+
35
type RequestInterface = typeof request;
46
type RequestOptions = {
57
request?: RequestInterface;
@@ -34,6 +36,8 @@ interface VerifyRequestByKeyIdInterface {
3436
): Promise<boolean>;
3537
}
3638

39+
// response types
40+
3741
export interface CreateAckEventInterface {
3842
(): ResponseEvent<"ack">
3943
}
@@ -126,6 +130,122 @@ interface CopilotReference {
126130
};
127131
}
128132

133+
// parse types
134+
135+
export interface CopilotRequestPayload {
136+
copilot_thread_id: string
137+
messages: Message[]
138+
stop: any
139+
top_p: number
140+
temperature: number
141+
max_tokens: number
142+
presence_penalty: number
143+
frequency_penalty: number
144+
copilot_skills: any[]
145+
agent: string
146+
}
147+
148+
export interface OpenAICompatibilityPayload {
149+
messages: {
150+
role: string
151+
name?: string
152+
content: string
153+
}[]
154+
}
155+
156+
export interface Message {
157+
role: string
158+
content: string
159+
copilot_references: MessageCopilotReference[]
160+
copilot_confirmations?: MessageCopilotConfirmation[]
161+
name?: string
162+
}
163+
164+
export interface MessageCopilotReference {
165+
type: string
166+
data: CopilotReferenceData
167+
id: string
168+
is_implicit: boolean
169+
metadata: CopilotReferenceMetadata
170+
}
171+
172+
export interface CopilotReferenceData {
173+
type: string
174+
id: number
175+
name?: string
176+
ownerLogin?: string
177+
ownerType?: string
178+
readmePath?: string
179+
description?: string
180+
commitOID?: string
181+
ref?: string
182+
refInfo?: CopilotReferenceDataRefInfo
183+
visibility?: string
184+
languages?: CopilotReferenceDataLanguage[]
185+
login?: string
186+
avatarURL?: string
187+
url?: string
188+
}
189+
190+
export interface CopilotReferenceDataRefInfo {
191+
name: string
192+
type: string
193+
}
194+
195+
export interface CopilotReferenceDataLanguage {
196+
name: string
197+
percent: number
198+
}
199+
200+
export interface CopilotReferenceMetadata {
201+
display_name: string
202+
display_icon: string
203+
display_url: string
204+
}
205+
206+
export interface MessageCopilotConfirmation {
207+
state: "dismissed" | "accepted"
208+
confirmation: {
209+
id: string
210+
[key: string]: unknown
211+
}
212+
}
213+
214+
export interface ParseRequestBodyInterface {
215+
(body: string): CopilotRequestPayload
216+
}
217+
218+
export interface TransformPayloadForOpenAICompatibilityInterface {
219+
(payload: CopilotRequestPayload): OpenAICompatibilityPayload
220+
}
221+
222+
223+
export interface VerifyAndParseRequestInterface {
224+
(
225+
body: string,
226+
signature: string,
227+
keyID: string,
228+
requestOptions?: RequestOptions,
229+
): Promise<{ isValidRequest: boolean; payload: CopilotRequestPayload }>;
230+
}
231+
232+
233+
export interface GetUserMessageInterface {
234+
(payload: CopilotRequestPayload): string;
235+
}
236+
237+
export type UserConfirmation = {
238+
accepted: boolean;
239+
id?: string;
240+
metadata: Record<string, unknown>;
241+
}
242+
243+
export interface GetUserConfirmationInterface {
244+
(payload: CopilotRequestPayload): UserConfirmation | undefined;
245+
}
246+
247+
// exported methods
248+
129249
export declare const verifyRequest: VerifyRequestInterface;
130250
export declare const fetchVerificationKeys: FetchVerificationKeysInterface;
131251
export declare const verifyRequestByKeyId: VerifyRequestByKeyIdInterface;
@@ -136,3 +256,9 @@ export declare const createDoneEvent: CreateDoneEventInterface;
136256
export declare const createErrorsEvent: CreateErrorsEventInterface;
137257
export declare const createReferencesEvent: CreateReferencesEventInterface;
138258
export declare const createTextEvent: CreateTextEventInterface;
259+
260+
export declare const parseRequestBody: ParseRequestBodyInterface;
261+
export declare const transformPayloadForOpenAICompatibility: TransformPayloadForOpenAICompatibilityInterface;
262+
export declare const verifyAndParseRequest: VerifyAndParseRequestInterface;
263+
export declare const getUserMessage: GetUserMessageInterface;
264+
export declare const getUserConfirmation: GetUserConfirmationInterface;

‎index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// @ts-check
22

3-
export * from "./lib/verification.js";
3+
export * from "./lib/parse.js";
44
export * from "./lib/response.js";
5+
export * from "./lib/verification.js";

‎index.test-d.ts

+46-6
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,18 @@ import {
1111
fetchVerificationKeys,
1212
verifyRequest,
1313
verifyRequestByKeyId,
14+
parseRequestBody,
15+
transformPayloadForOpenAICompatibility,
16+
verifyAndParseRequest,
17+
getUserMessage,
18+
getUserConfirmation,
1419
type VerificationPublicKey,
20+
CopilotRequestPayload,
1521
} from "./index.js";
1622

17-
const rawBody = "";
18-
const signature = "";
19-
const keyId = "";
20-
const key = ""
2123
const token = "";
2224

23-
export async function verifyRequestByKeyIdTest() {
25+
export async function verifyRequestByKeyIdTest(rawBody: string, signature: string, keyId: string) {
2426
const result = await verifyRequestByKeyId(rawBody, signature, keyId);
2527
expectType<boolean>(result);
2628

@@ -43,7 +45,7 @@ export async function verifyRequestByKeyIdTest() {
4345
await verifyRequestByKeyId(rawBody, signature, keyId, { request });
4446
}
4547

46-
export async function verifyRequestTest() {
48+
export async function verifyRequestTest(rawBody: string, signature: string, key: string) {
4749
const result = await verifyRequest(rawBody, signature, key);
4850
expectType<boolean>(result);
4951

@@ -227,3 +229,41 @@ export function createDoneEventTest() {
227229
// @ts-expect-error - .event is required
228230
event.event
229231
}
232+
233+
export function parseRequestBodyTest(body: string) {
234+
const result = parseRequestBody(body)
235+
expectType<CopilotRequestPayload>(result);
236+
}
237+
238+
export function transformPayloadForOpenAICompatibilityTest(payload: CopilotRequestPayload) {
239+
const result = transformPayloadForOpenAICompatibility(payload)
240+
expectType<{
241+
messages: {
242+
content: string;
243+
role: string;
244+
name?: string
245+
}[]
246+
}
247+
>(result);
248+
}
249+
250+
export async function verifyAndParseRequestTest(rawBody: string, signature: string, keyId: string) {
251+
const result = await verifyAndParseRequest(rawBody, signature, keyId)
252+
expectType<{ isValidRequest: boolean, payload: CopilotRequestPayload }>(result);
253+
}
254+
255+
export function getUserMessageTest(payload: CopilotRequestPayload) {
256+
const result = getUserMessage(payload)
257+
expectType<string>(result)
258+
}
259+
260+
export function getUserConfirmationTest(payload: CopilotRequestPayload) {
261+
const result = getUserConfirmation(payload)
262+
263+
if (result === undefined) {
264+
expectType<undefined>(result)
265+
return
266+
}
267+
268+
expectType<{ accepted: boolean; id?: string; metadata: Record<string, unknown> }>(result)
269+
}

‎lib/parse.js

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// @ts-check
2+
3+
import { verifyRequestByKeyId } from "./verification.js";
4+
5+
/** @type {import('..').ParseRequestBodyInterface} */
6+
export function parseRequestBody(body) {
7+
return JSON.parse(body);
8+
}
9+
10+
/** @type {import('..').TransformPayloadForOpenAICompatibilityInterface} */
11+
export function transformPayloadForOpenAICompatibility(payload) {
12+
return {
13+
messages: payload.messages.map((message) => {
14+
return {
15+
role: message.role,
16+
name: message.name,
17+
content: message.content,
18+
};
19+
}),
20+
};
21+
}
22+
23+
/** @type {import('..').VerifyAndParseRequestInterface} */
24+
export async function verifyAndParseRequest(body, signature, keyID, options) {
25+
const isValidRequest = await verifyRequestByKeyId(
26+
body,
27+
signature,
28+
keyID,
29+
options
30+
);
31+
32+
return {
33+
isValidRequest,
34+
payload: parseRequestBody(body),
35+
};
36+
}
37+
38+
/** @type {import('..').GetUserMessageInterface} */
39+
export function getUserMessage(payload) {
40+
return payload.messages[payload.messages.length - 1].content;
41+
}
42+
43+
/** @type {import('..').GetUserConfirmationInterface} */
44+
export function getUserConfirmation(payload) {
45+
const confirmation =
46+
payload.messages[payload.messages.length - 1].copilot_confirmations?.[0];
47+
48+
if (!confirmation) return;
49+
50+
const { id, ...metadata } = confirmation.confirmation;
51+
52+
return {
53+
accepted: confirmation.state === "accepted",
54+
id,
55+
metadata,
56+
};
57+
}

0 commit comments

Comments
 (0)
Failed to load comments.