Skip to content

Commit 730db8c

Browse files
authoredSep 2, 2024
feat: prompt() (copilot-extensions#39)
1 parent 1a47599 commit 730db8c

File tree

6 files changed

+262
-1
lines changed

6 files changed

+262
-1
lines changed
 

‎README.md

+97
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,23 @@ export default handler(request, response) {
3838
}
3939
```
4040

41+
### Send a custom propmt
42+
43+
```js
44+
import { prompt } from "@copilot-extensions/preview-sdk";
45+
46+
try {
47+
const { message } = await prompt("What is the capital of France?", {
48+
model: "gpt-4",
49+
token: process.env.TOKEN,
50+
});
51+
52+
console.log(message.content);
53+
} catch (error) {
54+
console.error(error);
55+
}
56+
```
57+
4158
## API
4259

4360
### Verification
@@ -285,6 +302,86 @@ if (userConfirmation) {
285302
}
286303
```
287304
305+
## Prompt (Custom Chat completion calls)
306+
307+
#### `prompt(message, options)`
308+
309+
Send a prompt to the chat UI and receive a response from the user. The `message` argument must be a string and may include markdown.
310+
311+
The `options` argument is optional. It can contain a `token` to authenticate the request to GitHub's API, or a custom `request.fetch` instance to use for the request.
312+
313+
```js
314+
import { prompt } from "@copilot-extensions/preview-sdk";
315+
316+
const { message } = await prompt("What is the capital of France?", {
317+
model: "gpt-4",
318+
token: process.env.TOKEN,
319+
});
320+
321+
console.log(message.content);
322+
```
323+
324+
⚠️ Not all of the arguments below are implemented yet.
325+
326+
```js
327+
await prompt({
328+
model: "gpt-4o",
329+
token: process.env.TOKEN,
330+
system: "You are a helpful assistant.",
331+
messages: [
332+
{ role: "user", content: "What is the capital of France?" },
333+
{ role: "assistant", content: "The capital of France is Paris." },
334+
{
335+
role: "user",
336+
content: [
337+
[
338+
{ type: "text", text: "What about this country?" },
339+
{
340+
type: "image_url",
341+
image_url: urlToImageOfFlagOfSpain,
342+
},
343+
],
344+
],
345+
},
346+
],
347+
// GitHub recommends using your GitHub username, the name of your application
348+
// https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api?apiVersion=2022-11-28#user-agent
349+
userAgent: "gr2m/my-app v1.2.3",
350+
// set an alternative chat completions endpoint
351+
endpoint: "https://models.inference.ai.azure.com/chat/completions",
352+
// compare https://platform.openai.com/docs/guides/function-calling/configuring-function-calling-behavior-using-the-tool_choice-parameter
353+
toolChoice: "auto",
354+
tools: [
355+
{
356+
type: "function",
357+
function: {
358+
name: "get_weather",
359+
strict: true,
360+
parameters: {
361+
type: "object",
362+
properties: {
363+
location: { type: "string" },
364+
unit: { type: "string", enum: ["c", "f"] },
365+
},
366+
required: ["location", "unit"],
367+
additionalProperties: False,
368+
},
369+
},
370+
},
371+
],
372+
// configuration related to the request transport layer
373+
request: {
374+
// for mocking, proxying, client certificates, etc.
375+
fetch: myCustomFetch,
376+
// hook into request life cycle for complex authentication strategies, retries, throttling, etc
377+
// compare options.request.hook from https://github.com/octokit/request.js
378+
hook: myCustomHook,
379+
// Use an `AbortController` instance to cancel a request
380+
signal: myAbortController.signal,
381+
},
382+
});
383+
```
384+
288385
## Dreamcode
289386
290387
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

+27-1
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,30 @@ export interface GetUserConfirmationInterface {
244244
(payload: CopilotRequestPayload): UserConfirmation | undefined;
245245
}
246246

247+
// prompt
248+
249+
/** model names supported by Copilot API */
250+
export type ModelName =
251+
| "gpt-4"
252+
| "gpt-3.5-turbo"
253+
254+
export type PromptOptions = {
255+
model: ModelName
256+
token: string
257+
request?: {
258+
fetch?: Function
259+
}
260+
}
261+
262+
export type PromptResult = {
263+
requestId: string
264+
message: Message
265+
}
266+
267+
interface PromptInterface {
268+
(userPrompt: string, options: PromptOptions): Promise<PromptResult>;
269+
}
270+
247271
// exported methods
248272

249273
export declare const verifyRequest: VerifyRequestInterface;
@@ -261,4 +285,6 @@ export declare const parseRequestBody: ParseRequestBodyInterface;
261285
export declare const transformPayloadForOpenAICompatibility: TransformPayloadForOpenAICompatibilityInterface;
262286
export declare const verifyAndParseRequest: VerifyAndParseRequestInterface;
263287
export declare const getUserMessage: GetUserMessageInterface;
264-
export declare const getUserConfirmation: GetUserConfirmationInterface;
288+
export declare const getUserConfirmation: GetUserConfirmationInterface;
289+
290+
export declare const prompt: PromptInterface;

‎index.js

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
export * from "./lib/parse.js";
44
export * from "./lib/response.js";
55
export * from "./lib/verification.js";
6+
export * from "./lib/prompt.js";

‎index.test-d.ts

+29
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
getUserConfirmation,
1919
type VerificationPublicKey,
2020
CopilotRequestPayload,
21+
prompt,
2122
} from "./index.js";
2223

2324
const token = "";
@@ -267,3 +268,31 @@ export function getUserConfirmationTest(payload: CopilotRequestPayload) {
267268

268269
expectType<{ accepted: boolean; id?: string; metadata: Record<string, unknown> }>(result)
269270
}
271+
272+
export async function promptTest() {
273+
const result = await prompt("What is the capital of France?", {
274+
model: "gpt-4",
275+
token: "secret",
276+
})
277+
278+
expectType<string>(result.requestId)
279+
expectType<string>(result.message.content)
280+
281+
// with custom fetch
282+
await prompt("What is the capital of France?", {
283+
model: "gpt-4",
284+
token: "secret",
285+
request: {
286+
fetch: () => { }
287+
}
288+
})
289+
290+
// @ts-expect-error - 2nd argument is required
291+
prompt("What is the capital of France?")
292+
293+
// @ts-expect-error - model argument is required
294+
prompt("What is the capital of France?", { token: "" })
295+
296+
// @ts-expect-error - token argument is required
297+
prompt("What is the capital of France?", { model: "" })
298+
}

‎lib/prompt.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// @ts-check
2+
3+
/** @type {import('..').PromptInterface} */
4+
export async function prompt(userPrompt, promptOptions) {
5+
const promptFetch = promptOptions.request?.fetch || fetch;
6+
const response = await promptFetch(
7+
"https://api.githubcopilot.com/chat/completions",
8+
{
9+
method: "POST",
10+
headers: {
11+
accept: "application/json",
12+
"content-type": "application/json; charset=UTF-8",
13+
"user-agent": "copilot-extensions/preview-sdk.js",
14+
authorization: `Bearer ${promptOptions.token}`,
15+
},
16+
body: JSON.stringify({
17+
messages: [
18+
{
19+
role: "system",
20+
content: "You are a helpful assistant.",
21+
},
22+
{
23+
role: "user",
24+
content: userPrompt,
25+
},
26+
],
27+
model: promptOptions.model,
28+
}),
29+
}
30+
);
31+
32+
const data = await response.json();
33+
34+
return {
35+
requestId: response.headers.get("x-request-id"),
36+
message: data.choices[0].message,
37+
};
38+
}

‎test/prompt.test.js

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { test } from "node:test";
2+
3+
import { MockAgent } from "undici";
4+
5+
import { prompt } from "../index.js";
6+
7+
test("smoke", (t) => {
8+
t.assert.equal(typeof prompt, "function");
9+
});
10+
11+
test("minimal usage", async (t) => {
12+
const mockAgent = new MockAgent();
13+
function fetchMock(url, opts) {
14+
opts ||= {};
15+
opts.dispatcher = mockAgent;
16+
return fetch(url, opts);
17+
}
18+
19+
mockAgent.disableNetConnect();
20+
const mockPool = mockAgent.get("https://api.githubcopilot.com");
21+
mockPool
22+
.intercept({
23+
method: "post",
24+
path: `/chat/completions`,
25+
body: JSON.stringify({
26+
messages: [
27+
{
28+
role: "system",
29+
content: "You are a helpful assistant.",
30+
},
31+
{
32+
role: "user",
33+
content: "What is the capital of France?",
34+
},
35+
],
36+
model: "gpt-4o-mini",
37+
}),
38+
})
39+
.reply(
40+
200,
41+
{
42+
choices: [
43+
{
44+
message: {
45+
content: "<response text>",
46+
},
47+
},
48+
],
49+
},
50+
{
51+
headers: {
52+
"content-type": "application/json",
53+
"x-request-id": "<request-id>",
54+
},
55+
}
56+
);
57+
58+
const result = await prompt("What is the capital of France?", {
59+
token: "secret",
60+
model: "gpt-4o-mini",
61+
request: { fetch: fetchMock },
62+
});
63+
64+
t.assert.deepEqual(result, {
65+
requestId: "<request-id>",
66+
message: {
67+
content: "<response text>",
68+
},
69+
});
70+
});

0 commit comments

Comments
 (0)
Failed to load comments.