Skip to content

Commit 2c0e04f

Browse files
authoredAug 31, 2024
feat: createAckEvent(), createTextEvent(), createConfirmationEvent(), createReferencesEvent(), createErrorsEvent(), createDoneEvent() (copilot-extensions#32)
1 parent 06310d7 commit 2c0e04f

13 files changed

+812
-83
lines changed
 

‎.github/workflows/test.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ jobs:
1212
strategy:
1313
matrix:
1414
node_version:
15-
- 20
15+
# v20 does not support snapshot testing which we use for the alpha version
16+
# - 20
1617
- 22
1718

1819
steps:

‎CONTRIBUTING.md

+35
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,38 @@ Thank you for considering to contribute to `github-project` 💖
44

55
Please note that this project is released with a [Contributor Code of Conduct](./CODE_OF_CONDUCT.md).
66
By participating you agree to abide by its terms.
7+
8+
## Ways to contribute
9+
10+
- **Reporting bugs** - if you find a bug, please [report it](https://github.com/copilot-extensions/preview-sdk.js/issues/new)!
11+
- **Suggesting features** - if you have an idea for a new feature, please [suggest it](https://github.com/copilot-extensions/preview-sdk.js/issues/new)!
12+
- **Contribute dreamcode** - like dreaming big? Same! Send a pull request with your beautiful API design that is so good, we just _have_ to make it real: [dreamcode.md](https://github.com/copilot-extensions/preview-sdk.js/blob/main/dreamcode.md)!
13+
- **Contribute code** - Yes! Please! We might even have [issues that are ready to be worked on](https://github.com/copilot-extensions/preview-sdk.js/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22pull%20request%20welcome%22)!
14+
15+
## Development
16+
17+
### Prerequisites
18+
19+
- [Node.js](https://nodejs.org/) (v22.x)
20+
21+
We currently depend on Node 22+ for local development as we use new test APIs such as [snapshot testing](https://nodejs.org/api/test.html#snapshot-testing)! At some point we might move to a different test runner, but this works great to move fast in early aplha days.
22+
23+
### Setup
24+
25+
Use a codespace and you are all set: https://github.com/copilot-extensions/preview-sdk.js/codespaces.
26+
27+
Or, if you prefer to develop locally:
28+
29+
```
30+
gh repo clone copilot-extensions/preview-sdk.js
31+
cd preview-sdk.js
32+
npm install
33+
```
34+
35+
### Running tests
36+
37+
```
38+
npm test
39+
```
40+
41+
As part of the tests, we test types for our public APIs (`npm run test:types`) and our code (`npm run test:tsc`). Run `npm run` to see all available scripts.

‎MAINTAINING.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Maintaining
2+
3+
## Merging Pull Request & releasing a new version
4+
5+
Releases are automated using [semantic-release](https://github.com/semantic-release/semantic-release).
6+
The following commit message conventions determine which version is released:
7+
8+
1. `fix: ...` or `fix(scope name): ...` prefix in subject: bumps fix version, e.g. `1.2.3``1.2.4`
9+
2. `feat: ...` or `feat(scope name): ...` prefix in subject: bumps feature version, e.g. `1.2.3``1.3.0`
10+
3. `BREAKING CHANGE:` in body: bumps breaking version, e.g. `1.2.3``2.0.0`
11+
12+
Only one version number is bumped at a time, the highest version change trumps the others.
13+
Besides, publishing a new version to npm, semantic-release also creates a git tag and release
14+
on GitHub, generates changelogs from the commit messages and puts them into the release notes.
15+
16+
If the pull request looks good but does not follow the commit conventions, update the pull request title and use the <kbd>Squash & merge</kbd> button, at which point you can set a custom commit message.

‎README.md

+126-3
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,27 @@ const payloadIsVerified = await verifyRequestByKeyId(
2222
// true or false
2323
```
2424

25+
### Build a response
26+
27+
```js
28+
import { createAckEvent, createDoneEvent, createTextEvent } from "@copilot-extensions/preview-sdk";
29+
30+
export default handler(request, response) {
31+
const ackEvent = createAckEvent();
32+
const textEvent = createTextEvent("Hello, world!");
33+
const doneEvent = createDoneEvent();
34+
35+
response.write(ackEvent.toString());
36+
response.write(textEvent.toString());
37+
response.end(doneEvent.toString());
38+
}
39+
```
40+
2541
## API
2642

27-
### `async verifyRequestByKeyId(rawBody, signature, keyId, options)`
43+
### Verification
44+
45+
#### `async verifyRequestByKeyId(rawBody, signature, keyId, options)`
2846

2947
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.
3048

@@ -46,7 +64,7 @@ await verifyRequestByKeyId(request.body, signature, key, { token: "ghp_1234" });
4664
await verifyRequestByKeyId(request.body, signature, key, { request });
4765
```
4866

49-
### `async fetchVerificationKeys(options)`
67+
#### `async fetchVerificationKeys(options)`
5068

5169
Fetches public keys for verifying copilot extension requests [from GitHub's API](https://api.github.com/meta/public_keys/copilot_api)
5270
and returns them as an array. The request can be made without authentication, with a token, or with a custom [octokit request](https://github.com/octokit/request.js) instance.
@@ -64,7 +82,7 @@ const [current] = await fetchVerificationKeys({ token: "ghp_1234" });
6482
const [current] = await fetchVerificationKeys({ request });)
6583
```
6684

67-
### `async verifyRequestPayload(rawBody, signature, keyId)`
85+
#### `async verifyRequestPayload(rawBody, signature, keyId)`
6886

6987
Verify the request payload using the provided signature and key. Note that the raw body as received by GitHub must be passed, before any parsing.
7088

@@ -79,6 +97,111 @@ const payloadIsVerified = await verifyRequestPayload(
7997
// true or false
8098
```
8199

100+
### Response
101+
102+
All `create*Event()` methods return an object with a `.toString()` method, which is called automatically when a string is expected. Unfortunately that's not the case for `response.write()`, you need to call `.toString()` explicitly.
103+
104+
#### `createAckEvent()`
105+
106+
Acknowledge the request so that the chat UI can tell the user that the agent started generating a response.
107+
The `ack` event should only be sent once.
108+
109+
```js
110+
import { createAckEvent } from "@copilot-extensions/preview-sdk";
111+
112+
response.write(createAckEvent().toString());
113+
```
114+
115+
#### `createTextEvent(message)`
116+
117+
Send a text message to the chat UI. Multiple messages can be sent. The `message` argument must be a string and may include markdown.
118+
119+
```js
120+
import { createTextEvent } from "@copilot-extensions/preview-sdk";
121+
122+
response.write(createTextEvent("Hello, world!").toString());
123+
response.write(createTextEvent("Hello, again!").toString());
124+
```
125+
126+
#### `createConfirmationEvent({ id, title, message, metadata })`
127+
128+
Ask the user to confirm an action. The `confirmation` event should only be sent once.
129+
130+
The `meta` data object will be sent along the user's response.
131+
132+
See additional documentation about Copilot confirmations at https://github.com/github/copilot-partners/blob/main/docs/confirmations.md.
133+
134+
```js
135+
import { createConfirmationEvent } from "@copilot-extensions/preview-sdk";
136+
137+
response.write(
138+
createConfirmationEvent({
139+
id: "123",
140+
title: "Are you sure?",
141+
message: "This will do something.",
142+
}).toString()
143+
);
144+
```
145+
146+
#### `createReferencesEvent(references)`
147+
148+
Send a list of references to the chat UI. The `references` argument must be an array of objects with the following properties:
149+
150+
- `id`
151+
- `type`
152+
153+
The following properties are optional
154+
155+
- `data`: object with any properties.
156+
- `is_implicit`: a boolean
157+
- `metadata`: an object with a required `display_name` and the optional properties: `display_icon` and `display_url`
158+
159+
Multiple `references` events can be sent.
160+
161+
See additional documentation about Copilot references at https://github.com/github/copilot-partners/blob/main/docs/copilot-references.md.
162+
163+
```js
164+
import { createReferencesEvent } from "@copilot-extensions/preview-sdk";
165+
166+
response.write(
167+
createReferencesEvent([
168+
{
169+
id: "123",
170+
type: "issue",
171+
data: {
172+
number: 123,
173+
},
174+
is_implicit: false,
175+
metadata: {
176+
display_name: "My issue",
177+
display_icon: "issue-opened",
178+
display_url: "https://github.com/monalisa/hello-world/issues/123",
179+
},
180+
]).toString()
181+
);
182+
```
183+
184+
#### `createErrorsEvent(errors)`
185+
186+
An array of objects with the following properties:
187+
188+
- `type`: must be one of: `"reference"`, `"function"`, `"agent"`
189+
- `code`
190+
- `message`
191+
- `identifier`
192+
193+
See additional documentation about Copilot errors at https://github.com/github/copilot-partners/blob/main/docs/copilot-errors.md.
194+
195+
#### `createDoneEvent()`
196+
197+
The `done` event should only be sent once, at the end of the response. No further events can be sent after the `done` event.
198+
199+
```js
200+
import { createDoneEvent } from "@copilot-extensions/preview-sdk";
201+
202+
response.write(createDoneEvent().toString());
203+
```
204+
82205
## Dreamcode
83206
84207
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

+99
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,105 @@ interface VerifyRequestByKeyIdInterface {
3434
): Promise<boolean>;
3535
}
3636

37+
export interface CreateAckEventInterface {
38+
(): ResponseEvent<"ack">
39+
}
40+
41+
export interface CreateTextEventInterface {
42+
(message: string): ResponseEvent<"text">
43+
}
44+
45+
export type CreateConfirmationEventOptions = { id: string, title: string, message: string, metadata?: Record<string, unknown> }
46+
47+
export interface CreateConfirmationEventInterface {
48+
(options: CreateConfirmationEventOptions): ResponseEvent<"copilot_confirmation">
49+
}
50+
export interface CreateReferencesEventInterface {
51+
(references: CopilotReference[]): ResponseEvent<"copilot_references">
52+
}
53+
export interface CreateErrorsEventInterface {
54+
(errors: CopilotError[]): ResponseEvent<"copilot_errors">
55+
}
56+
export interface CreateDoneEventInterface {
57+
(): ResponseEvent<"done">
58+
}
59+
60+
type ResponseEventType = "ack" | "done" | "text" | "copilot_references" | "copilot_confirmation" | "copilot_errors"
61+
type EventsWithoutEventKey = "ack" | "done" | "text"
62+
type ResponseEvent<T extends ResponseEventType = "text"> =
63+
T extends EventsWithoutEventKey ? {
64+
data: T extends "ack" ? CopilotAckResponseEventData : T extends "done" ? CopilotDoneResponseEventData : T extends "text" ? CopilotTextResponseEventData : never
65+
toString: () => string
66+
} : {
67+
event: T
68+
data: T extends "copilot_references" ? CopilotReferenceResponseEventData : T extends "copilot_confirmation" ? CopilotConfirmationResponseEventData : T extends "copilot_errors" ? CopilotErrorsResponseEventData : never
69+
toString: () => string
70+
}
71+
72+
type CopilotAckResponseEventData = {
73+
choices: [{
74+
delta: {
75+
content: "", role: "assistant"
76+
}
77+
}]
78+
}
79+
80+
type CopilotDoneResponseEventData = {
81+
choices: [{
82+
finish_reason: "stop"
83+
delta: {
84+
content: null
85+
}
86+
}]
87+
}
88+
89+
type CopilotTextResponseEventData = {
90+
choices: [{
91+
delta: {
92+
content: string, role: "assistant"
93+
}
94+
}]
95+
}
96+
type CopilotConfirmationResponseEventData = {
97+
type: 'action';
98+
title: string;
99+
message: string;
100+
confirmation?: {
101+
id: string;
102+
[key: string]: any;
103+
};
104+
}
105+
type CopilotErrorsResponseEventData = CopilotError[]
106+
type CopilotReferenceResponseEventData = CopilotReference[]
107+
108+
type CopilotError = {
109+
type: "reference" | "function" | "agent";
110+
code: string;
111+
message: string;
112+
identifier: string;
113+
}
114+
115+
interface CopilotReference {
116+
type: string;
117+
id: string;
118+
data?: {
119+
[key: string]: unknown;
120+
};
121+
is_implicit?: boolean;
122+
metadata?: {
123+
display_name: string;
124+
display_icon?: string;
125+
display_url?: string;
126+
};
127+
}
128+
37129
export declare const verifyRequest: VerifyRequestInterface;
38130
export declare const fetchVerificationKeys: FetchVerificationKeysInterface;
39131
export declare const verifyRequestByKeyId: VerifyRequestByKeyIdInterface;
132+
133+
export declare const createAckEvent: CreateAckEventInterface;
134+
export declare const createConfirmationEvent: CreateConfirmationEventInterface;
135+
export declare const createDoneEvent: CreateDoneEventInterface;
136+
export declare const createErrorsEvent: CreateErrorsEventInterface;
137+
export declare const createReferencesEvent: CreateReferencesEventInterface;
138+
export declare const createTextEvent: CreateTextEventInterface;

0 commit comments

Comments
 (0)
Failed to load comments.