Skip to content

Commit 9533a1f

Browse files
authoredSep 5, 2024
feat(prompt): prompt.stream(), options.endpoint (copilot-extensions#57)
BREAKING CHANGE: `prompt()` is now throwing an error if the API responds with an error status code
1 parent f8eec8f commit 9533a1f

File tree

5 files changed

+245
-58
lines changed

5 files changed

+245
-58
lines changed
 

‎README.md

+16
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,22 @@ await prompt({
422422
});
423423
```
424424
425+
#### `prompt.stream(message, options)`
426+
427+
Works the same way as `prompt()`, but resolves with a `stream` key instead of a `message` key.
428+
429+
```js
430+
import { prompt } from "@copilot-extensions/preview-sdk";
431+
432+
const { requestId, stream } = prompt.stream("What is the capital of France?", {
433+
token: process.env.TOKEN,
434+
});
435+
436+
for await (const chunk of stream) {
437+
console.log(new TextDecoder().decode(chunk));
438+
}
439+
```
440+
425441
### `getFunctionCalls()`
426442
427443
Convenience metthod if a result from a `prompt()` call includes function calls.

‎index.d.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export interface OpenAICompatibilityPayload {
175175
export interface CopilotMessage {
176176
role: string;
177177
content: string;
178-
copilot_references: MessageCopilotReference[];
178+
copilot_references?: MessageCopilotReference[];
179179
copilot_confirmations?: MessageCopilotConfirmation[];
180180
tool_calls?: {
181181
function: {
@@ -300,8 +300,9 @@ export interface PromptFunction {
300300
}
301301

302302
export type PromptOptions = {
303-
model?: ModelName;
304303
token: string;
304+
endpoint?: string;
305+
model?: ModelName;
305306
tools?: PromptFunction[];
306307
messages?: InteropMessage[];
307308
request?: {
@@ -314,12 +315,25 @@ export type PromptResult = {
314315
message: CopilotMessage;
315316
};
316317

318+
export type PromptStreamResult = {
319+
requestId: string;
320+
stream: ReadableStream<Uint8Array>;
321+
};
322+
317323
// https://stackoverflow.com/a/69328045
318324
type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
319325

320326
interface PromptInterface {
321327
(userPrompt: string, options: PromptOptions): Promise<PromptResult>;
322328
(options: WithRequired<PromptOptions, "messages">): Promise<PromptResult>;
329+
stream: PromptStreamInterface;
330+
}
331+
332+
interface PromptStreamInterface {
333+
(userPrompt: string, options: PromptOptions): Promise<PromptStreamResult>;
334+
(
335+
options: WithRequired<PromptOptions, "messages">,
336+
): Promise<PromptStreamResult>;
323337
}
324338

325339
interface GetFunctionCallsInterface {

‎index.test-d.ts

+18-3
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,6 @@ export function getUserConfirmationTest(payload: CopilotRequestPayload) {
302302

303303
export async function promptTest() {
304304
const result = await prompt("What is the capital of France?", {
305-
model: "gpt-4",
306305
token: "secret",
307306
});
308307

@@ -311,7 +310,6 @@ export async function promptTest() {
311310

312311
// with custom fetch
313312
await prompt("What is the capital of France?", {
314-
model: "gpt-4",
315313
token: "secret",
316314
request: {
317315
fetch: () => {},
@@ -327,7 +325,6 @@ export async function promptTest() {
327325

328326
export async function promptWithToolsTest() {
329327
await prompt("What is the capital of France?", {
330-
model: "gpt-4",
331328
token: "secret",
332329
tools: [
333330
{
@@ -366,6 +363,24 @@ export async function promptWithoutMessageButMessages() {
366363
});
367364
}
368365

366+
export async function otherPromptOptionsTest() {
367+
const result = await prompt("What is the capital of France?", {
368+
token: "secret",
369+
model: "gpt-4",
370+
endpoint: "https://api.githubcopilot.com",
371+
});
372+
}
373+
374+
export async function promptStreamTest() {
375+
const result = await prompt.stream("What is the capital of France?", {
376+
model: "gpt-4",
377+
token: "secret",
378+
});
379+
380+
expectType<string>(result.requestId);
381+
expectType<ReadableStream<Uint8Array>>(result.stream);
382+
}
383+
369384
export async function getFunctionCallsTest(
370385
promptResponsePayload: PromptResult,
371386
) {

‎lib/prompt.js

+81-33
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
// @ts-check
22

33
/** @type {import('..').PromptInterface} */
4-
export async function prompt(userPrompt, promptOptions) {
5-
const options = typeof userPrompt === "string" ? promptOptions : userPrompt;
64

7-
const promptFetch = options.request?.fetch || fetch;
8-
const modelName = options.model || "gpt-4";
5+
function parsePromptArguments(userPrompt, promptOptions) {
6+
const { request: requestOptions, ...options } =
7+
typeof userPrompt === "string" ? promptOptions : userPrompt;
8+
9+
const promptFetch = requestOptions?.fetch || fetch;
10+
const model = options.model || "gpt-4";
11+
const endpoint =
12+
options.endpoint || "https://api.githubcopilot.com/chat/completions";
913

1014
const systemMessage = options.tools
1115
? "You are a helpful assistant. Use the supplied tools to assist the user."
1216
: "You are a helpful assistant.";
17+
const toolsChoice = options.tools ? "auto" : undefined;
1318

1419
const messages = [
1520
{
@@ -29,44 +34,87 @@ export async function prompt(userPrompt, promptOptions) {
2934
});
3035
}
3136

32-
const response = await promptFetch(
33-
"https://api.githubcopilot.com/chat/completions",
34-
{
35-
method: "POST",
36-
headers: {
37-
accept: "application/json",
38-
"content-type": "application/json; charset=UTF-8",
39-
"user-agent": "copilot-extensions/preview-sdk.js",
40-
authorization: `Bearer ${options.token}`,
41-
},
42-
body: JSON.stringify({
43-
messages: messages,
44-
model: modelName,
45-
toolChoice: options.tools ? "auto" : undefined,
46-
tools: options.tools,
47-
}),
48-
}
49-
);
37+
return [promptFetch, { ...options, messages, model, endpoint, toolsChoice }];
38+
}
5039

51-
if (response.ok) {
52-
const data = await response.json();
40+
async function sendPromptRequest(promptFetch, options) {
41+
const { endpoint, token, ...payload } = options;
42+
const method = "POST";
43+
const headers = {
44+
accept: "application/json",
45+
"content-type": "application/json; charset=UTF-8",
46+
"user-agent": "copilot-extensions/preview-sdk.js",
47+
authorization: `Bearer ${token}`,
48+
};
5349

54-
return {
55-
requestId: response.headers.get("x-request-id"),
56-
message: data.choices[0].message,
57-
};
50+
const response = await promptFetch(endpoint, {
51+
method,
52+
headers,
53+
body: JSON.stringify(payload),
54+
});
55+
56+
if (response.ok) {
57+
return response;
5858
}
5959

60+
const body = await response.text();
61+
console.log({ body });
62+
63+
throw Object.assign(
64+
new Error(
65+
`[@copilot-extensions/preview-sdk] An error occured with the chat completions API`,
66+
),
67+
{
68+
name: "PromptError",
69+
request: {
70+
method: "POST",
71+
url: endpoint,
72+
headers: {
73+
...headers,
74+
authorization: `Bearer [REDACTED]`,
75+
},
76+
body: payload,
77+
},
78+
response: {
79+
status: response.status,
80+
headers: [...response.headers],
81+
body: body,
82+
},
83+
},
84+
);
85+
}
86+
export async function prompt(userPrompt, promptOptions) {
87+
const [promptFetch, options] = parsePromptArguments(
88+
userPrompt,
89+
promptOptions,
90+
);
91+
const response = await sendPromptRequest(promptFetch, options);
6092
const requestId = response.headers.get("x-request-id");
93+
94+
const data = await response.json();
95+
6196
return {
62-
requestId: requestId,
63-
message: {
64-
role: "Sssistant",
65-
content: `Sorry, an error occured with the chat completions API. (Status: ${response.status}, request ID: ${requestId})`,
66-
},
97+
requestId,
98+
message: data.choices[0].message,
6799
};
68100
}
69101

102+
prompt.stream = async function promptStream(userPrompt, promptOptions) {
103+
const [promptFetch, options] = parsePromptArguments(
104+
userPrompt,
105+
promptOptions,
106+
);
107+
const response = await sendPromptRequest(promptFetch, {
108+
...options,
109+
stream: true,
110+
});
111+
112+
return {
113+
requestId: response.headers.get("x-request-id"),
114+
stream: response.body,
115+
};
116+
};
117+
70118
/** @type {import('..').GetFunctionCallsInterface} */
71119
export function getFunctionCalls(payload) {
72120
const functionCalls = payload.message.tool_calls;

0 commit comments

Comments
 (0)
Failed to load comments.