@copilot-extensions/preview-sdk
Advanced tools
Comparing version 2.6.0 to 2.6.1
122
dreamcode.md
@@ -13,5 +13,9 @@ # Copilot Extension Dreamcode | ||
- High-level API for interacting with models | ||
- High-level API for function calls | ||
- High-level API for requesting user confirmation before a function is called | ||
## API | ||
## Examples | ||
### Tell a joke | ||
```js | ||
@@ -69,3 +73,115 @@ import { createServer } from "http"; | ||
); | ||
``` | ||
### Book a flight | ||
I'm using [@daveebbelaar](https://github.com/daveebbelaar)'s example of a flight booking agent that they demonstrate at https://www.youtube.com/watch?v=aqdWSYWC_LI | ||
```js | ||
import { createServer } from "http"; | ||
import { | ||
CopilotExtension, | ||
createNodeMiddleware, | ||
} from "@octokit/copilot-extension"; | ||
const copilotExtension = new CopilotExtension({ | ||
userAgent: "book-a-flight", | ||
// TBD: are we supporting a default model? Globally, or for an enterprise/organization/user? | ||
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? | ||
name: "gpt-4", | ||
// optional, setting to default for demo purposes | ||
endpoint: "https://api.githubcopilot.com/chat/completions", | ||
// when enabled, messages are passed through to Copilot's chat completions API | ||
// defaults to false. Set to true when `functions` is set | ||
passThrough: true, | ||
}, | ||
functions: [ | ||
{ | ||
name: "lookup_flight", | ||
description: "Look up a flight based on time, origin, and destination", | ||
parameters: { | ||
time: { | ||
type: "string", | ||
description: | ||
"The time when the flight should depart as ISO 8601 date time string", | ||
}, | ||
origin: { | ||
type: "string", | ||
description: "The airport short code for the origin of the flight", | ||
}, | ||
destination: { | ||
type: "string", | ||
description: | ||
"The airport short code for the destination of the flight", | ||
}, | ||
}, | ||
async run({ time, origin, destination }) { | ||
const result = await myFlightLookupFunction(time, origin, destination); | ||
return { | ||
departureTime: result.departureTime, | ||
timezoneDifference: result.timezoneDifference, | ||
arrivalTime: result.arrivalTime, | ||
travelTime: result.travelTime, | ||
flightNumber: result.flightNumber, | ||
airline: result.airline, | ||
originCity: result.originCity, | ||
originCode: result.originCode, | ||
destinationCity: result.destinationCity, | ||
destinationCode: result.destinationCode, | ||
travelTime: result.travelTime, | ||
}; | ||
}, | ||
}, | ||
{ | ||
name: "book_flight", | ||
description: "Book a flight based flight number and day", | ||
parameters: { | ||
flightNumber: { | ||
type: "string", | ||
description: "The flight number", | ||
}, | ||
date: { | ||
type: "string", | ||
description: "The date of the flight as an ISO 8601 date string", | ||
}, | ||
}, | ||
// setting a confirmation key will prompt the user to confirm an action before it is taken | ||
confirmation: { | ||
title: "Confirm you want me to book the following flight", | ||
message(parameters) { | ||
return `Yes, please book flight ${parameters.flightNumber} on ${parameters.date}`; | ||
}, | ||
}, | ||
async run({ flightNumber, date }) { | ||
const result = await myFlightBookingFunction(flightNumber, date); | ||
return { | ||
flightNumber, | ||
departureTime: result.date, | ||
confirmationNumber: result.confirmationNumber, | ||
seat: result.seat, | ||
}; | ||
}, | ||
}, | ||
], | ||
}); | ||
// you can still hook into messages and function calls before they are passed through | ||
// to the chat completions API. | ||
copilotExtension.on("message", async ({ log }) => { | ||
log.info("Received a message:", message.content); | ||
// if you don't want a request to be forwarded to the chat completions API, call `await respond.done()` explicitly | ||
}); | ||
copilotExtension.on("function_call", async ({ log, name, parameters }) => { | ||
log.info( | ||
"Received a function call for %s with parameters %o", | ||
name, | ||
parameters | ||
); | ||
}); | ||
createServer(createNodeMiddleware(copilotExtension)).listen(3000); | ||
@@ -98,3 +214,5 @@ copilotExtension.log.info("Listening on http://localhost:3000"); | ||
- `prompt` is based on my work at https://github.com/github/gr2m-projects/blob/167/github-models/167-github-models/README.md. A simple API to interact with GitHub models. I assume we will default the prompt URL to `https://api.githubcopilot.com/chat/completions` and the model to `gpt-4o` (or whatever our CAPI name for that is?) | ||
- The `prompt` API will automatically apply interop transformations if the request is sent to an endpoint other than Copilot's chat complitions endpoint. | ||
- The `prompt` API | ||
- will automatically apply interop transformations if the request is sent to an endpoint other than Copilot's chat complitions endpoint. | ||
- will automatically pass through past messages unless explicitly overridden | ||
- `respond` is an API to send different types of responses to the user | ||
@@ -101,0 +219,0 @@ - `log` is the logger as we use it in Octokit. See https://github.com/octokit/core.js?tab=readme-ov-file#logging |
@@ -259,17 +259,8 @@ import { request } from "@octokit/request"; | ||
* model names supported by Copilot API | ||
* | ||
* Based on https://api.githubcopilot.com/models from 2024-09-02 | ||
* A list of currently supported models can be retrieved at | ||
* 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 = | ||
| "gpt-3.5-turbo" | ||
| "gpt-3.5-turbo-0613" | ||
| "gpt-4" | ||
| "gpt-4-0613" | ||
| "gpt-4-o-preview" | ||
| "gpt-4o" | ||
| "gpt-4o-2024-05-13" | ||
| "text-embedding-3-small" | ||
| "text-embedding-3-small-inference" | ||
| "text-embedding-ada-002" | ||
| "text-embedding-ada-002-index" | ||
export type ModelName = string | ||
@@ -288,3 +279,3 @@ export interface PromptFunction { | ||
export type PromptOptions = { | ||
model: ModelName | ||
model?: ModelName | ||
token: string | ||
@@ -291,0 +282,0 @@ tools?: PromptFunction[] |
@@ -294,5 +294,2 @@ import { expectType } from "tsd"; | ||
// @ts-expect-error - model argument is required | ||
prompt("What is the capital of France?", { token: "" }) | ||
// @ts-expect-error - token argument is required | ||
@@ -299,0 +296,0 @@ prompt("What is the capital of France?", { model: "" }) |
@@ -8,2 +8,3 @@ // @ts-check | ||
const promptFetch = options.request?.fetch || fetch; | ||
const modelName = options.model || "gpt-4"; | ||
@@ -44,3 +45,3 @@ const systemMessage = options.tools | ||
messages: messages, | ||
model: options.model, | ||
model: modelName, | ||
toolChoice: options.tools ? "auto" : undefined, | ||
@@ -47,0 +48,0 @@ tools: options.tools, |
@@ -8,3 +8,3 @@ { | ||
"type": "module", | ||
"version": "2.6.0", | ||
"version": "2.6.1", | ||
"keywords": [ | ||
@@ -11,0 +11,0 @@ "ai", |
@@ -48,3 +48,2 @@ # `@copilot-extensions/preview-sdk` | ||
const { message } = await prompt("What is the capital of France?", { | ||
model: "gpt-4", | ||
token: process.env.TOKEN, | ||
@@ -51,0 +50,0 @@ }); |
@@ -61,3 +61,2 @@ import { test, suite } from "node:test"; | ||
token: "secret", | ||
model: "gpt-4", | ||
request: { fetch: fetchMock }, | ||
@@ -74,2 +73,63 @@ }); | ||
test("options.prompt", 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://api.githubcopilot.com"); | ||
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?" }, | ||
{ role: "assistant", content: "The capital of France is Paris." }, | ||
{ role: "user", content: "What about Spain?" }, | ||
], | ||
model: "<custom-model>", | ||
}), | ||
}) | ||
.reply( | ||
200, | ||
{ | ||
choices: [ | ||
{ | ||
message: { | ||
content: "<response text>", | ||
}, | ||
}, | ||
], | ||
}, | ||
{ | ||
headers: { | ||
"content-type": "application/json", | ||
"x-request-id": "<request-id>", | ||
}, | ||
} | ||
); | ||
const result = await prompt("What about Spain?", { | ||
model: "<custom-model>", | ||
token: "secret", | ||
messages: [ | ||
{ role: "user", content: "What is the capital of France?" }, | ||
{ role: "assistant", content: "The capital of France is Paris." }, | ||
], | ||
request: { fetch: fetchMock }, | ||
}); | ||
t.assert.deepEqual(result, { | ||
requestId: "<request-id>", | ||
message: { | ||
content: "<response text>", | ||
}, | ||
}); | ||
}); | ||
test("options.messages", async (t) => { | ||
@@ -119,3 +179,2 @@ const mockAgent = new MockAgent(); | ||
const result = await prompt("What about Spain?", { | ||
model: "gpt-4", | ||
token: "secret", | ||
@@ -181,3 +240,2 @@ messages: [ | ||
const result = await prompt({ | ||
model: "gpt-4", | ||
token: "secret", | ||
@@ -254,3 +312,2 @@ messages: [ | ||
token: "secret", | ||
model: "gpt-4", | ||
tools: [ | ||
@@ -313,3 +370,2 @@ { | ||
token: "secret", | ||
model: "gpt-4", | ||
request: { fetch: fetchMock }, | ||
@@ -316,0 +372,0 @@ }); |
85069
1633
441
10