@copilot-extensions/preview-sdk
Advanced tools
Comparing version 2.6.1 to 3.0.0
@@ -23,33 +23,27 @@ # Copilot Extension Dreamcode | ||
import { | ||
CopilotExtension, | ||
createNodeMiddleware, | ||
} from "@octokit/copilot-extension"; | ||
import { CopilotAgent, createNodeMiddleware } from "@octokit/copilot-extension"; | ||
const copilotExtension = new CopilotExtension({ | ||
const agent = new CopilotAgent({ | ||
userAgent: "my-app-name", | ||
}); | ||
copilotExtension.on( | ||
"message", | ||
async ({ message, octokit, prompt, respond, log }) => { | ||
log.info("Received a message:", message.content); | ||
agent.on("message", async ({ message, octokit, prompt, respond, log }) => { | ||
log.info("Received a message:", message.content); | ||
const { data: user } = await octokit.request("GET /user"); | ||
await respond.text(`Hello, ${user.login}!`); | ||
const { data: user } = await octokit.request("GET /user"); | ||
await respond.text(`Hello, ${user.login}!`); | ||
await respond.confirmation({ | ||
title: "Would you like to hear a joke?", | ||
message: "I have a joke about construction, but I'm still working on it.", | ||
id: "joke", | ||
// optional | ||
meta: { | ||
other: "data", | ||
}, | ||
}); | ||
} | ||
); | ||
await respond.confirmation({ | ||
title: "Would you like to hear a joke?", | ||
message: "I have a joke about construction, but I'm still working on it.", | ||
id: "joke", | ||
// optional | ||
meta: { | ||
other: "data", | ||
}, | ||
}); | ||
}); | ||
// https://github.com/github/copilot-partners/blob/6d1cde3a1abb147da53f1a39864661dc824d40b5/docs/confirmations.md | ||
copilotExtension.on( | ||
agent.on( | ||
"confirmation", | ||
@@ -64,3 +58,5 @@ async ({ confirmation, octokit, prompt, respond, log }) => { | ||
await respond.text( | ||
prompt.stream("Please tell me a joke about Mona Lisa, Github's mascot.") | ||
prompt.stream( | ||
"Please tell me a joke about Mona Lisa, Github's mascot.", | ||
), | ||
); | ||
@@ -72,4 +68,7 @@ return; | ||
await respond.text("Hmm, something went wrong. Please try again later."); | ||
} | ||
}, | ||
); | ||
createServer(createNodeMiddleware(agent)).listen(3000); | ||
agent.log.info("Listening on http://localhost:3000"); | ||
``` | ||
@@ -84,8 +83,5 @@ | ||
import { | ||
CopilotExtension, | ||
createNodeMiddleware, | ||
} from "@octokit/copilot-extension"; | ||
import { CopilotAgent, createNodeMiddleware } from "@octokit/copilot-extension"; | ||
const copilotExtension = new CopilotExtension({ | ||
const copilotAgent = new CopilotAgent({ | ||
userAgent: "book-a-flight", | ||
@@ -95,5 +91,5 @@ | ||
model: { | ||
// either "gpt-3.5-turbo" or "gpt-4". Maybe "default" if we support that server-side or want to support that in the SDK? | ||
// Defaults to "gpt-4". Get available models from https://api.githubcopilot.com/models | ||
name: "gpt-4", | ||
// optional, setting to default for demo purposes | ||
// Defaults to "https://api.githubcopilot.com/chat/completions" | ||
endpoint: "https://api.githubcopilot.com/chat/completions", | ||
@@ -177,3 +173,3 @@ // when enabled, messages are passed through to Copilot's chat completions API | ||
// to the chat completions API. | ||
copilotExtension.on("message", async ({ log }) => { | ||
copilotAgent.on("message", async ({ log }) => { | ||
log.info("Received a message:", message.content); | ||
@@ -183,12 +179,12 @@ | ||
}); | ||
copilotExtension.on("function_call", async ({ log, name, parameters }) => { | ||
copilotAgent.on("function_call", async ({ log, name, parameters }) => { | ||
log.info( | ||
"Received a function call for %s with parameters %o", | ||
name, | ||
parameters | ||
parameters, | ||
); | ||
}); | ||
createServer(createNodeMiddleware(copilotExtension)).listen(3000); | ||
copilotExtension.log.info("Listening on http://localhost:3000"); | ||
createServer(createNodeMiddleware(copilotAgent)).listen(3000); | ||
copilotAgent.log.info("Listening on http://localhost:3000"); | ||
``` | ||
@@ -200,11 +196,11 @@ | ||
// verify the payload and call handlers | ||
await copilotExtension.verifyAndReceive({ payload, signature, keyId }); | ||
await copilotAgent.verifyAndReceive({ payload, signature, keyId }); | ||
// same, but skip verification | ||
await copilotExtension.receive({ payload }); | ||
await copilotAgent.receive({ payload }); | ||
// and if you don't want to use the event-based API | ||
const { isValidRequest, payload } = await copilotExtension.verifyAndParse( | ||
const { isValidRequest, payload } = await copilotAgent.verifyAndParse( | ||
payload, | ||
signature, | ||
keyId | ||
keyId, | ||
); | ||
@@ -211,0 +207,0 @@ ``` |
299
index.d.ts
@@ -17,13 +17,7 @@ import { request } from "@octokit/request"; | ||
interface VerifyRequestInterface { | ||
( | ||
rawBody: string, | ||
signature: string, | ||
key: string | ||
): Promise<boolean>; | ||
(rawBody: string, signature: string, key: string): Promise<boolean>; | ||
} | ||
interface FetchVerificationKeysInterface { | ||
( | ||
requestOptions?: RequestOptions, | ||
): Promise<VerificationPublicKey[]>; | ||
(requestOptions?: RequestOptions): Promise<VerificationPublicKey[]>; | ||
} | ||
@@ -43,58 +37,91 @@ | ||
export interface CreateAckEventInterface { | ||
(): ResponseEvent<"ack"> | ||
(): ResponseEvent<"ack">; | ||
} | ||
export interface CreateTextEventInterface { | ||
(message: string): ResponseEvent<"text"> | ||
(message: string): ResponseEvent<"text">; | ||
} | ||
export type CreateConfirmationEventOptions = { id: string, title: string, message: string, metadata?: Record<string, unknown> } | ||
export type CreateConfirmationEventOptions = { | ||
id: string; | ||
title: string; | ||
message: string; | ||
metadata?: Record<string, unknown>; | ||
}; | ||
export interface CreateConfirmationEventInterface { | ||
(options: CreateConfirmationEventOptions): ResponseEvent<"copilot_confirmation"> | ||
( | ||
options: CreateConfirmationEventOptions, | ||
): ResponseEvent<"copilot_confirmation">; | ||
} | ||
export interface CreateReferencesEventInterface { | ||
(references: CopilotReference[]): ResponseEvent<"copilot_references"> | ||
(references: CopilotReference[]): ResponseEvent<"copilot_references">; | ||
} | ||
export interface CreateErrorsEventInterface { | ||
(errors: CopilotError[]): ResponseEvent<"copilot_errors"> | ||
(errors: CopilotError[]): ResponseEvent<"copilot_errors">; | ||
} | ||
export interface CreateDoneEventInterface { | ||
(): ResponseEvent<"done"> | ||
(): ResponseEvent<"done">; | ||
} | ||
type ResponseEventType = "ack" | "done" | "text" | "copilot_references" | "copilot_confirmation" | "copilot_errors" | ||
type EventsWithoutEventKey = "ack" | "done" | "text" | ||
type ResponseEventType = | ||
| "ack" | ||
| "done" | ||
| "text" | ||
| "copilot_references" | ||
| "copilot_confirmation" | ||
| "copilot_errors"; | ||
type EventsWithoutEventKey = "ack" | "done" | "text"; | ||
type ResponseEvent<T extends ResponseEventType = "text"> = | ||
T extends EventsWithoutEventKey ? { | ||
data: T extends "ack" ? CopilotAckResponseEventData : T extends "done" ? CopilotDoneResponseEventData : T extends "text" ? CopilotTextResponseEventData : never | ||
toString: () => string | ||
} : { | ||
event: T | ||
data: T extends "copilot_references" ? CopilotReferenceResponseEventData : T extends "copilot_confirmation" ? CopilotConfirmationResponseEventData : T extends "copilot_errors" ? CopilotErrorsResponseEventData : never | ||
toString: () => string | ||
} | ||
T extends EventsWithoutEventKey | ||
? { | ||
data: T extends "ack" | ||
? CopilotAckResponseEventData | ||
: T extends "done" | ||
? CopilotDoneResponseEventData | ||
: T extends "text" | ||
? CopilotTextResponseEventData | ||
: never; | ||
toString: () => string; | ||
} | ||
: { | ||
event: T; | ||
data: T extends "copilot_references" | ||
? CopilotReferenceResponseEventData | ||
: T extends "copilot_confirmation" | ||
? CopilotConfirmationResponseEventData | ||
: T extends "copilot_errors" | ||
? CopilotErrorsResponseEventData | ||
: never; | ||
toString: () => string; | ||
}; | ||
type CopilotAckResponseEventData = { | ||
choices: [{ | ||
delta: InteropMessage<"assistant"> | ||
}] | ||
} | ||
choices: [ | ||
{ | ||
delta: InteropMessage<"assistant">; | ||
}, | ||
]; | ||
}; | ||
type CopilotDoneResponseEventData = { | ||
choices: [{ | ||
finish_reason: "stop" | ||
delta: { | ||
content: null | ||
} | ||
}] | ||
} | ||
choices: [ | ||
{ | ||
finish_reason: "stop"; | ||
delta: { | ||
content: null; | ||
}; | ||
}, | ||
]; | ||
}; | ||
type CopilotTextResponseEventData = { | ||
choices: [{ | ||
delta: InteropMessage<"assistant"> | ||
}] | ||
} | ||
choices: [ | ||
{ | ||
delta: InteropMessage<"assistant">; | ||
}, | ||
]; | ||
}; | ||
type CopilotConfirmationResponseEventData = { | ||
type: 'action'; | ||
type: "action"; | ||
title: string; | ||
@@ -106,5 +133,5 @@ message: string; | ||
}; | ||
} | ||
type CopilotErrorsResponseEventData = CopilotError[] | ||
type CopilotReferenceResponseEventData = CopilotReference[] | ||
}; | ||
type CopilotErrorsResponseEventData = CopilotError[]; | ||
type CopilotReferenceResponseEventData = CopilotReference[]; | ||
@@ -116,3 +143,3 @@ type CopilotError = { | ||
identifier: string; | ||
} | ||
}; | ||
@@ -136,101 +163,100 @@ interface CopilotReference { | ||
export interface CopilotRequestPayload { | ||
copilot_thread_id: string | ||
messages: CopilotMessage[] | ||
stop: any | ||
top_p: number | ||
temperature: number | ||
max_tokens: number | ||
presence_penalty: number | ||
frequency_penalty: number | ||
copilot_skills: any[] | ||
agent: string | ||
copilot_thread_id: string; | ||
messages: CopilotMessage[]; | ||
stop: any; | ||
top_p: number; | ||
temperature: number; | ||
max_tokens: number; | ||
presence_penalty: number; | ||
frequency_penalty: number; | ||
copilot_skills: any[]; | ||
agent: string; | ||
} | ||
export interface OpenAICompatibilityPayload { | ||
messages: InteropMessage[] | ||
messages: InteropMessage[]; | ||
} | ||
export interface CopilotMessage { | ||
role: string | ||
content: string | ||
copilot_references: MessageCopilotReference[] | ||
copilot_confirmations?: MessageCopilotConfirmation[] | ||
role: string; | ||
content: string; | ||
copilot_references?: MessageCopilotReference[]; | ||
copilot_confirmations?: MessageCopilotConfirmation[]; | ||
tool_calls?: { | ||
"function": { | ||
"arguments": string, | ||
"name": string | ||
}, | ||
"id": string, | ||
"type": "function" | ||
}[] | ||
name?: string | ||
[key: string]: unknown | ||
function: { | ||
arguments: string; | ||
name: string; | ||
}; | ||
id: string; | ||
type: "function"; | ||
}[]; | ||
name?: string; | ||
[key: string]: unknown; | ||
} | ||
export interface InteropMessage<TRole extends string = string> { | ||
role: TRole | ||
content: string | ||
name?: string | ||
[key: string]: unknown | ||
role: TRole; | ||
content: string; | ||
name?: string; | ||
[key: string]: unknown; | ||
} | ||
export interface MessageCopilotReference { | ||
type: string | ||
data: CopilotReferenceData | ||
id: string | ||
is_implicit: boolean | ||
metadata: CopilotReferenceMetadata | ||
type: string; | ||
data: CopilotReferenceData; | ||
id: string; | ||
is_implicit: boolean; | ||
metadata: CopilotReferenceMetadata; | ||
} | ||
export interface CopilotReferenceData { | ||
type: string | ||
id: number | ||
name?: string | ||
ownerLogin?: string | ||
ownerType?: string | ||
readmePath?: string | ||
description?: string | ||
commitOID?: string | ||
ref?: string | ||
refInfo?: CopilotReferenceDataRefInfo | ||
visibility?: string | ||
languages?: CopilotReferenceDataLanguage[] | ||
login?: string | ||
avatarURL?: string | ||
url?: string | ||
type: string; | ||
id: number; | ||
name?: string; | ||
ownerLogin?: string; | ||
ownerType?: string; | ||
readmePath?: string; | ||
description?: string; | ||
commitOID?: string; | ||
ref?: string; | ||
refInfo?: CopilotReferenceDataRefInfo; | ||
visibility?: string; | ||
languages?: CopilotReferenceDataLanguage[]; | ||
login?: string; | ||
avatarURL?: string; | ||
url?: string; | ||
} | ||
export interface CopilotReferenceDataRefInfo { | ||
name: string | ||
type: string | ||
name: string; | ||
type: string; | ||
} | ||
export interface CopilotReferenceDataLanguage { | ||
name: string | ||
percent: number | ||
name: string; | ||
percent: number; | ||
} | ||
export interface CopilotReferenceMetadata { | ||
display_name: string | ||
display_icon: string | ||
display_url: string | ||
display_name: string; | ||
display_icon: string; | ||
display_url: string; | ||
} | ||
export interface MessageCopilotConfirmation { | ||
state: "dismissed" | "accepted" | ||
state: "dismissed" | "accepted"; | ||
confirmation: { | ||
id: string | ||
[key: string]: unknown | ||
} | ||
id: string; | ||
[key: string]: unknown; | ||
}; | ||
} | ||
export interface ParseRequestBodyInterface { | ||
(body: string): CopilotRequestPayload | ||
(body: string): CopilotRequestPayload; | ||
} | ||
export interface TransformPayloadForOpenAICompatibilityInterface { | ||
(payload: CopilotRequestPayload): OpenAICompatibilityPayload | ||
(payload: CopilotRequestPayload): OpenAICompatibilityPayload; | ||
} | ||
export interface VerifyAndParseRequestInterface { | ||
@@ -245,3 +271,2 @@ ( | ||
export interface GetUserMessageInterface { | ||
@@ -255,3 +280,3 @@ (payload: CopilotRequestPayload): string; | ||
metadata: Record<string, unknown>; | ||
} | ||
}; | ||
@@ -264,13 +289,13 @@ export interface GetUserConfirmationInterface { | ||
/** | ||
/** | ||
* model names supported by Copilot API | ||
* A list of currently supported models can be retrieved at | ||
* https://api.githubcopilot.com/models. We set `ModelName` to `string` | ||
* https://api.githubcopilot.com/models. We set `ModelName` to `string` | ||
* instead of a union of the supported models as we cannot give | ||
* guarantees about the supported models in the future. | ||
*/ | ||
export type ModelName = string | ||
export type ModelName = string; | ||
export interface PromptFunction { | ||
type: "function" | ||
type: "function"; | ||
function: { | ||
@@ -282,22 +307,28 @@ name: string; | ||
strict?: boolean | null; | ||
} | ||
}; | ||
} | ||
export type PromptOptions = { | ||
model?: ModelName | ||
token: string | ||
tools?: PromptFunction[] | ||
messages?: InteropMessage[] | ||
token: string; | ||
endpoint?: string; | ||
model?: ModelName; | ||
tools?: PromptFunction[]; | ||
messages?: InteropMessage[]; | ||
request?: { | ||
fetch?: Function | ||
} | ||
} | ||
fetch?: Function; | ||
}; | ||
}; | ||
export type PromptResult = { | ||
requestId: string | ||
message: CopilotMessage | ||
} | ||
requestId: string; | ||
message: CopilotMessage; | ||
}; | ||
export type PromptStreamResult = { | ||
requestId: string; | ||
stream: ReadableStream<Uint8Array>; | ||
}; | ||
// https://stackoverflow.com/a/69328045 | ||
type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] } | ||
type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] }; | ||
@@ -307,4 +338,12 @@ interface PromptInterface { | ||
(options: WithRequired<PromptOptions, "messages">): Promise<PromptResult>; | ||
stream: PromptStreamInterface; | ||
} | ||
interface PromptStreamInterface { | ||
(userPrompt: string, options: PromptOptions): Promise<PromptStreamResult>; | ||
( | ||
options: WithRequired<PromptOptions, "messages">, | ||
): Promise<PromptStreamResult>; | ||
} | ||
interface GetFunctionCallsInterface { | ||
@@ -314,6 +353,6 @@ (payload: PromptResult): { | ||
function: { | ||
name: string, | ||
arguments: string, | ||
} | ||
}[] | ||
name: string; | ||
arguments: string; | ||
}; | ||
}[]; | ||
} | ||
@@ -341,2 +380,2 @@ | ||
export declare const prompt: PromptInterface; | ||
export declare const getFunctionCalls: GetFunctionCallsInterface; | ||
export declare const getFunctionCalls: GetFunctionCallsInterface; |
@@ -29,3 +29,7 @@ import { expectType } from "tsd"; | ||
export async function verifyRequestByKeyIdTest(rawBody: string, signature: string, keyId: string) { | ||
export async function verifyRequestByKeyIdTest( | ||
rawBody: string, | ||
signature: string, | ||
keyId: string, | ||
) { | ||
const result = await verifyRequestByKeyId(rawBody, signature, keyId); | ||
@@ -53,3 +57,7 @@ expectType<boolean>(result); | ||
export async function verifyRequestTest(rawBody: string, signature: string, key: string) { | ||
export async function verifyRequestTest( | ||
rawBody: string, | ||
signature: string, | ||
key: string, | ||
) { | ||
const result = await verifyRequest(rawBody, signature, key); | ||
@@ -87,11 +95,12 @@ expectType<boolean>(result); | ||
expectType<{ | ||
choices: [{ | ||
delta: InteropMessage<"assistant"> | ||
}] | ||
choices: [ | ||
{ | ||
delta: InteropMessage<"assistant">; | ||
}, | ||
]; | ||
}>(event.data); | ||
// @ts-expect-error - .event is required | ||
event.event | ||
event.event; | ||
} | ||
@@ -105,9 +114,11 @@ | ||
expectType<{ | ||
choices: [{ | ||
delta: InteropMessage<"assistant"> | ||
}] | ||
choices: [ | ||
{ | ||
delta: InteropMessage<"assistant">; | ||
}, | ||
]; | ||
}>(event.data); | ||
// @ts-expect-error - .event is required | ||
event.event | ||
event.event; | ||
} | ||
@@ -119,3 +130,3 @@ | ||
title: "test", | ||
message: "test" | ||
message: "test", | ||
}); | ||
@@ -130,4 +141,4 @@ | ||
someOtherId: "test", | ||
} | ||
}) | ||
}, | ||
}); | ||
@@ -138,3 +149,3 @@ expectType<() => string>(event.toString); | ||
expectType<{ | ||
type: 'action'; | ||
type: "action"; | ||
title: string; | ||
@@ -174,15 +185,17 @@ message: string; | ||
expectType<{ | ||
type: string; | ||
id: string; | ||
data?: { | ||
[key: string]: unknown; | ||
}; | ||
is_implicit?: boolean; | ||
metadata?: { | ||
display_name: string; | ||
display_icon?: string; | ||
display_url?: string; | ||
}; | ||
}[]>(event.data); | ||
expectType< | ||
{ | ||
type: string; | ||
id: string; | ||
data?: { | ||
[key: string]: unknown; | ||
}; | ||
is_implicit?: boolean; | ||
metadata?: { | ||
display_name: string; | ||
display_icon?: string; | ||
display_url?: string; | ||
}; | ||
}[] | ||
>(event.data); | ||
@@ -193,27 +206,33 @@ expectType<"copilot_references">(event.event); | ||
export function createErrorsEventTest() { | ||
const event = createErrorsEvent([{ | ||
type: "reference", | ||
code: "1", | ||
message: "test reference error", | ||
identifier: "reference-identifier", | ||
}, { | ||
type: "function", | ||
code: "1", | ||
message: "test function error", | ||
identifier: "function-identifier", | ||
}, { | ||
type: "agent", | ||
code: "1", | ||
message: "test agent error", | ||
identifier: "agent-identifier", | ||
}]); | ||
const event = createErrorsEvent([ | ||
{ | ||
type: "reference", | ||
code: "1", | ||
message: "test reference error", | ||
identifier: "reference-identifier", | ||
}, | ||
{ | ||
type: "function", | ||
code: "1", | ||
message: "test function error", | ||
identifier: "function-identifier", | ||
}, | ||
{ | ||
type: "agent", | ||
code: "1", | ||
message: "test agent error", | ||
identifier: "agent-identifier", | ||
}, | ||
]); | ||
expectType<() => string>(event.toString); | ||
expectType<string>(event.toString()); | ||
expectType<{ | ||
type: "reference" | "function" | "agent"; | ||
code: string; | ||
message: string; | ||
identifier: string; | ||
}[]>(event.data); | ||
expectType< | ||
{ | ||
type: "reference" | "function" | "agent"; | ||
code: string; | ||
message: string; | ||
identifier: string; | ||
}[] | ||
>(event.data); | ||
@@ -229,23 +248,25 @@ expectType<"copilot_errors">(event.event); | ||
expectType<{ | ||
"choices": [ | ||
choices: [ | ||
{ | ||
"finish_reason": "stop", | ||
"delta": { | ||
"content": null | ||
} | ||
} | ||
] | ||
finish_reason: "stop"; | ||
delta: { | ||
content: null; | ||
}; | ||
}, | ||
]; | ||
}>(event.data); | ||
// @ts-expect-error - .event is required | ||
event.event | ||
event.event; | ||
} | ||
export function parseRequestBodyTest(body: string) { | ||
const result = parseRequestBody(body) | ||
const result = parseRequestBody(body); | ||
expectType<CopilotRequestPayload>(result); | ||
} | ||
export function transformPayloadForOpenAICompatibilityTest(payload: CopilotRequestPayload) { | ||
const result = transformPayloadForOpenAICompatibility(payload) | ||
export function transformPayloadForOpenAICompatibilityTest( | ||
payload: CopilotRequestPayload, | ||
) { | ||
const result = transformPayloadForOpenAICompatibility(payload); | ||
expectType<{ | ||
@@ -255,28 +276,37 @@ messages: { | ||
role: string; | ||
name?: string | ||
[key: string]: unknown | ||
}[] | ||
} | ||
>(result); | ||
name?: string; | ||
[key: string]: unknown; | ||
}[]; | ||
}>(result); | ||
} | ||
export async function verifyAndParseRequestTest(rawBody: string, signature: string, keyId: string) { | ||
const result = await verifyAndParseRequest(rawBody, signature, keyId) | ||
expectType<{ isValidRequest: boolean, payload: CopilotRequestPayload }>(result); | ||
export async function verifyAndParseRequestTest( | ||
rawBody: string, | ||
signature: string, | ||
keyId: string, | ||
) { | ||
const result = await verifyAndParseRequest(rawBody, signature, keyId); | ||
expectType<{ isValidRequest: boolean; payload: CopilotRequestPayload }>( | ||
result, | ||
); | ||
} | ||
export function getUserMessageTest(payload: CopilotRequestPayload) { | ||
const result = getUserMessage(payload) | ||
expectType<string>(result) | ||
const result = getUserMessage(payload); | ||
expectType<string>(result); | ||
} | ||
export function getUserConfirmationTest(payload: CopilotRequestPayload) { | ||
const result = getUserConfirmation(payload) | ||
const result = getUserConfirmation(payload); | ||
if (result === undefined) { | ||
expectType<undefined>(result) | ||
return | ||
expectType<undefined>(result); | ||
return; | ||
} | ||
expectType<{ accepted: boolean; id?: string; metadata: Record<string, unknown> }>(result) | ||
expectType<{ | ||
accepted: boolean; | ||
id?: string; | ||
metadata: Record<string, unknown>; | ||
}>(result); | ||
} | ||
@@ -286,23 +316,21 @@ | ||
const result = await prompt("What is the capital of France?", { | ||
model: "gpt-4", | ||
token: "secret", | ||
}) | ||
}); | ||
expectType<string>(result.requestId) | ||
expectType<string>(result.message.content) | ||
expectType<string>(result.requestId); | ||
expectType<string>(result.message.content); | ||
// with custom fetch | ||
await prompt("What is the capital of France?", { | ||
model: "gpt-4", | ||
token: "secret", | ||
request: { | ||
fetch: () => { } | ||
} | ||
}) | ||
fetch: () => {}, | ||
}, | ||
}); | ||
// @ts-expect-error - 2nd argument is required | ||
prompt("What is the capital of France?") | ||
prompt("What is the capital of France?"); | ||
// @ts-expect-error - token argument is required | ||
prompt("What is the capital of France?", { model: "" }) | ||
prompt("What is the capital of France?", { model: "" }); | ||
} | ||
@@ -312,3 +340,2 @@ | ||
await prompt("What is the capital of France?", { | ||
model: "gpt-4", | ||
token: "secret", | ||
@@ -323,6 +350,6 @@ tools: [ | ||
strict: true, | ||
} | ||
} | ||
] | ||
}) | ||
}, | ||
}, | ||
], | ||
}); | ||
} | ||
@@ -333,3 +360,3 @@ | ||
model: "gpt-4", | ||
token: 'secret', | ||
token: "secret", | ||
messages: [ | ||
@@ -345,3 +372,3 @@ { role: "user", content: "What is the capital of France?" }, | ||
model: "gpt-4", | ||
token: 'secret', | ||
token: "secret", | ||
messages: [ | ||
@@ -355,11 +382,34 @@ { role: "user", content: "What is the capital of France?" }, | ||
export async function getFunctionCallsTest(promptResponsePayload: PromptResult) { | ||
const result = getFunctionCalls(promptResponsePayload) | ||
export async function otherPromptOptionsTest() { | ||
const result = await prompt("What is the capital of France?", { | ||
token: "secret", | ||
model: "gpt-4", | ||
endpoint: "https://api.githubcopilot.com", | ||
}); | ||
} | ||
expectType<{ | ||
id: string, function: { | ||
name: string, | ||
arguments: string, | ||
} | ||
}[]>(result) | ||
} | ||
export async function promptStreamTest() { | ||
const result = await prompt.stream("What is the capital of France?", { | ||
model: "gpt-4", | ||
token: "secret", | ||
}); | ||
expectType<string>(result.requestId); | ||
expectType<ReadableStream<Uint8Array>>(result.stream); | ||
} | ||
export async function getFunctionCallsTest( | ||
promptResponsePayload: PromptResult, | ||
) { | ||
const result = getFunctionCalls(promptResponsePayload); | ||
expectType< | ||
{ | ||
id: string; | ||
function: { | ||
name: string; | ||
arguments: string; | ||
}; | ||
}[] | ||
>(result); | ||
} |
// @ts-check | ||
/** @type {import('..').PromptInterface} */ | ||
export async function prompt(userPrompt, promptOptions) { | ||
const options = typeof userPrompt === "string" ? promptOptions : userPrompt; | ||
const promptFetch = options.request?.fetch || fetch; | ||
const modelName = options.model || "gpt-4"; | ||
function parsePromptArguments(userPrompt, promptOptions) { | ||
const { request: requestOptions, ...options } = | ||
typeof userPrompt === "string" ? promptOptions : userPrompt; | ||
const promptFetch = requestOptions?.fetch || fetch; | ||
const model = options.model || "gpt-4"; | ||
const endpoint = | ||
options.endpoint || "https://api.githubcopilot.com/chat/completions"; | ||
const systemMessage = options.tools | ||
? "You are a helpful assistant. Use the supplied tools to assist the user." | ||
: "You are a helpful assistant."; | ||
const toolsChoice = options.tools ? "auto" : undefined; | ||
@@ -32,40 +37,83 @@ const messages = [ | ||
const response = await promptFetch( | ||
"https://api.githubcopilot.com/chat/completions", | ||
return [promptFetch, { ...options, messages, model, endpoint, toolsChoice }]; | ||
} | ||
async function sendPromptRequest(promptFetch, options) { | ||
const { endpoint, token, ...payload } = options; | ||
const method = "POST"; | ||
const headers = { | ||
accept: "application/json", | ||
"content-type": "application/json; charset=UTF-8", | ||
"user-agent": "copilot-extensions/preview-sdk.js", | ||
authorization: `Bearer ${token}`, | ||
}; | ||
const response = await promptFetch(endpoint, { | ||
method, | ||
headers, | ||
body: JSON.stringify(payload), | ||
}); | ||
if (response.ok) { | ||
return response; | ||
} | ||
const body = await response.text(); | ||
console.log({ body }); | ||
throw Object.assign( | ||
new Error( | ||
`[@copilot-extensions/preview-sdk] An error occured with the chat completions API`, | ||
), | ||
{ | ||
method: "POST", | ||
headers: { | ||
accept: "application/json", | ||
"content-type": "application/json; charset=UTF-8", | ||
"user-agent": "copilot-extensions/preview-sdk.js", | ||
authorization: `Bearer ${options.token}`, | ||
name: "PromptError", | ||
request: { | ||
method: "POST", | ||
url: endpoint, | ||
headers: { | ||
...headers, | ||
authorization: `Bearer [REDACTED]`, | ||
}, | ||
body: payload, | ||
}, | ||
body: JSON.stringify({ | ||
messages: messages, | ||
model: modelName, | ||
toolChoice: options.tools ? "auto" : undefined, | ||
tools: options.tools, | ||
}), | ||
} | ||
response: { | ||
status: response.status, | ||
headers: [...response.headers], | ||
body: body, | ||
}, | ||
}, | ||
); | ||
} | ||
export async function prompt(userPrompt, promptOptions) { | ||
const [promptFetch, options] = parsePromptArguments( | ||
userPrompt, | ||
promptOptions, | ||
); | ||
const response = await sendPromptRequest(promptFetch, options); | ||
const requestId = response.headers.get("x-request-id"); | ||
if (response.ok) { | ||
const data = await response.json(); | ||
const data = await response.json(); | ||
return { | ||
requestId: response.headers.get("x-request-id"), | ||
message: data.choices[0].message, | ||
}; | ||
} | ||
const requestId = response.headers.get("x-request-id"); | ||
return { | ||
requestId: requestId, | ||
message: { | ||
role: "Sssistant", | ||
content: `Sorry, an error occured with the chat completions API. (Status: ${response.status}, request ID: ${requestId})`, | ||
}, | ||
requestId, | ||
message: data.choices[0].message, | ||
}; | ||
} | ||
prompt.stream = async function promptStream(userPrompt, promptOptions) { | ||
const [promptFetch, options] = parsePromptArguments( | ||
userPrompt, | ||
promptOptions, | ||
); | ||
const response = await sendPromptRequest(promptFetch, { | ||
...options, | ||
stream: true, | ||
}); | ||
return { | ||
requestId: response.headers.get("x-request-id"), | ||
stream: response.body, | ||
}; | ||
}; | ||
/** @type {import('..').GetFunctionCallsInterface} */ | ||
@@ -72,0 +120,0 @@ export function getFunctionCalls(payload) { |
@@ -8,3 +8,3 @@ { | ||
"type": "module", | ||
"version": "2.6.1", | ||
"version": "3.0.0", | ||
"keywords": [ | ||
@@ -11,0 +11,0 @@ "ai", |
@@ -5,4 +5,18 @@ # `@copilot-extensions/preview-sdk` | ||
⚠️ **This SDK is a preview and subject to change**. We will however adhere to [semantic versioning](https://semver.org/), so it's save to use for early experimentation. Just beware there will be breaking changes. Best to watch this repository's releases for updates. | ||
This SDK simplifies the process of building GitHub Copilot Extensions. Building Copilot Extensions previously required manual handling of request verification, response formatting, and API interactions. This SDK simplifies these tasks, allowing you to focus on your extension's core functionality rather than building boilerplate code. Use it to integrate your tools, APIs, or data sources directly into Copilot Chat. | ||
We consider this SDK alpha software in terms of API stability, but we adhere to semantic-versioning, so it's safe to use today. | ||
## Key features | ||
- Request payload verification | ||
- Payload parsing | ||
- Response building | ||
## Benefits | ||
- Handles security and response formatting requirements | ||
- Provides utilities for common extension tasks | ||
- Streamlines the development process | ||
## Usage | ||
@@ -21,3 +35,3 @@ | ||
token: process.env.GITHUB_TOKEN, | ||
} | ||
}, | ||
); | ||
@@ -77,3 +91,3 @@ // true or false | ||
signature, | ||
key | ||
key, | ||
); | ||
@@ -116,3 +130,3 @@ | ||
signature, | ||
key | ||
key, | ||
); | ||
@@ -164,3 +178,3 @@ // true or false | ||
message: "This will do something.", | ||
}).toString() | ||
}).toString(), | ||
); | ||
@@ -273,3 +287,3 @@ ``` | ||
signature, | ||
key | ||
key, | ||
); | ||
@@ -417,2 +431,18 @@ | ||
#### `prompt.stream(message, options)` | ||
Works the same way as `prompt()`, but resolves with a `stream` key instead of a `message` key. | ||
```js | ||
import { prompt } from "@copilot-extensions/preview-sdk"; | ||
const { requestId, stream } = prompt.stream("What is the capital of France?", { | ||
token: process.env.TOKEN, | ||
}); | ||
for await (const chunk of stream) { | ||
console.log(new TextDecoder().decode(chunk)); | ||
} | ||
``` | ||
### `getFunctionCalls()` | ||
@@ -419,0 +449,0 @@ |
@@ -81,3 +81,3 @@ import { test, suite } from "node:test"; | ||
}, | ||
} | ||
}, | ||
); | ||
@@ -94,3 +94,3 @@ const testRequest = defaultRequest.defaults({ | ||
{ isValidRequest: true, payload: JSON.parse(RAW_BODY) }, | ||
result | ||
result, | ||
); | ||
@@ -141,5 +141,5 @@ }); | ||
}, | ||
result | ||
result, | ||
); | ||
}); | ||
}); |
import { test, suite } from "node:test"; | ||
import assert from "node:assert"; | ||
@@ -56,3 +57,3 @@ import { MockAgent } from "undici"; | ||
}, | ||
} | ||
}, | ||
); | ||
@@ -88,2 +89,3 @@ | ||
body: JSON.stringify({ | ||
model: "<custom-model>", | ||
messages: [ | ||
@@ -95,3 +97,2 @@ { role: "system", content: "You are a helpful assistant." }, | ||
], | ||
model: "<custom-model>", | ||
}), | ||
@@ -115,3 +116,3 @@ }) | ||
}, | ||
} | ||
}, | ||
); | ||
@@ -177,3 +178,3 @@ | ||
}, | ||
} | ||
}, | ||
); | ||
@@ -198,2 +199,63 @@ | ||
test("options.endpoint", async (t) => { | ||
const mockAgent = new MockAgent(); | ||
function fetchMock(url, opts) { | ||
opts ||= {}; | ||
opts.dispatcher = mockAgent; | ||
return fetch(url, opts); | ||
} | ||
mockAgent.disableNetConnect(); | ||
const mockPool = mockAgent.get("https://my-copilot-endpoint.test"); | ||
mockPool | ||
.intercept({ | ||
method: "post", | ||
path: `/chat/completions`, | ||
body: JSON.stringify({ | ||
messages: [ | ||
{ | ||
role: "system", | ||
content: "You are a helpful assistant.", | ||
}, | ||
{ | ||
role: "user", | ||
content: "What is the capital of France?", | ||
}, | ||
], | ||
model: "gpt-4", | ||
}), | ||
}) | ||
.reply( | ||
200, | ||
{ | ||
choices: [ | ||
{ | ||
message: { | ||
content: "<response text>", | ||
}, | ||
}, | ||
], | ||
}, | ||
{ | ||
headers: { | ||
"content-type": "application/json", | ||
"x-request-id": "<request-id>", | ||
}, | ||
}, | ||
); | ||
const result = await prompt("What is the capital of France?", { | ||
token: "secret", | ||
endpoint: "https://my-copilot-endpoint.test/chat/completions", | ||
request: { fetch: fetchMock }, | ||
}); | ||
t.assert.deepEqual(result, { | ||
requestId: "<request-id>", | ||
message: { | ||
content: "<response text>", | ||
}, | ||
}); | ||
}); | ||
test("single options argument", async (t) => { | ||
@@ -239,3 +301,3 @@ const mockAgent = new MockAgent(); | ||
}, | ||
} | ||
}, | ||
); | ||
@@ -276,2 +338,8 @@ | ||
body: JSON.stringify({ | ||
tools: [ | ||
{ | ||
type: "function", | ||
function: { name: "the_function", description: "The function" }, | ||
}, | ||
], | ||
messages: [ | ||
@@ -286,9 +354,3 @@ { | ||
model: "gpt-4", | ||
toolChoice: "auto", | ||
tools: [ | ||
{ | ||
type: "function", | ||
function: { name: "the_function", description: "The function" }, | ||
}, | ||
], | ||
toolsChoice: "auto", | ||
}), | ||
@@ -312,3 +374,3 @@ }) | ||
}, | ||
} | ||
}, | ||
); | ||
@@ -373,15 +435,47 @@ | ||
const result = await prompt("What is the capital of France?", { | ||
token: "secret", | ||
request: { fetch: fetchMock }, | ||
}); | ||
t.assert.deepEqual(result, { | ||
message: { | ||
content: | ||
"Sorry, an error occured with the chat completions API. (Status: 400, request ID: <request-id>)", | ||
role: "Sssistant", | ||
await assert.rejects( | ||
async () => { | ||
await prompt("What is the capital of France?", { | ||
token: "secret", | ||
request: { fetch: fetchMock }, | ||
}); | ||
}, | ||
requestId: "<request-id>", | ||
}); | ||
{ | ||
name: "PromptError", | ||
message: | ||
"[@copilot-extensions/preview-sdk] An error occured with the chat completions API", | ||
request: { | ||
method: "POST", | ||
url: "https://api.githubcopilot.com/chat/completions", | ||
headers: { | ||
"content-type": "application/json; charset=UTF-8", | ||
"user-agent": "copilot-extensions/preview-sdk.js", | ||
accept: "application/json", | ||
authorization: "Bearer [REDACTED]", | ||
}, | ||
body: { | ||
messages: [ | ||
{ | ||
content: "You are a helpful assistant.", | ||
role: "system", | ||
}, | ||
{ | ||
content: "What is the capital of France?", | ||
role: "user", | ||
}, | ||
], | ||
model: "gpt-4", | ||
toolsChoice: undefined, | ||
}, | ||
}, | ||
response: { | ||
status: 400, | ||
headers: [ | ||
["content-type", "text/plain"], | ||
["x-request-id", "<request-id>"], | ||
], | ||
body: "Bad Request", | ||
}, | ||
}, | ||
); | ||
}); | ||
@@ -419,3 +513,3 @@ | ||
}; | ||
}) | ||
}), | ||
); | ||
@@ -422,0 +516,0 @@ }); |
@@ -61,3 +61,3 @@ import { test } from "node:test"; | ||
}, | ||
} | ||
}, | ||
); | ||
@@ -175,3 +175,3 @@ const testRequest = defaultRequest.defaults({ | ||
}, | ||
} | ||
}, | ||
); | ||
@@ -178,0 +178,0 @@ const testRequest = defaultRequest.defaults({ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
90838
1854
471
11