diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ee41920..ee7f691 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,8 @@ jobs: strategy: matrix: node_version: - - 20 + # v20 does not support snapshot testing which we use for the alpha version + # - 20 - 22 steps: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 41c73e3..6687a93 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,3 +4,38 @@ Thank you for considering to contribute to `github-project` 💖 Please note that this project is released with a [Contributor Code of Conduct](./CODE_OF_CONDUCT.md). By participating you agree to abide by its terms. + +## Ways to contribute + +- **Reporting bugs** - if you find a bug, please [report it](https://github.com/copilot-extensions/preview-sdk.js/issues/new)! +- **Suggesting features** - if you have an idea for a new feature, please [suggest it](https://github.com/copilot-extensions/preview-sdk.js/issues/new)! +- **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)! +- **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)! + +## Development + +### Prerequisites + +- [Node.js](https://nodejs.org/) (v22.x) + + 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. + +### Setup + +Use a codespace and you are all set: https://github.com/copilot-extensions/preview-sdk.js/codespaces. + +Or, if you prefer to develop locally: + +``` +gh repo clone copilot-extensions/preview-sdk.js +cd preview-sdk.js +npm install +``` + +### Running tests + +``` +npm test +``` + +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. diff --git a/MAINTAINING.md b/MAINTAINING.md new file mode 100644 index 0000000..135e586 --- /dev/null +++ b/MAINTAINING.md @@ -0,0 +1,16 @@ +# Maintaining + +## Merging Pull Request & releasing a new version + +Releases are automated using [semantic-release](https://github.com/semantic-release/semantic-release). +The following commit message conventions determine which version is released: + +1. `fix: ...` or `fix(scope name): ...` prefix in subject: bumps fix version, e.g. `1.2.3` → `1.2.4` +2. `feat: ...` or `feat(scope name): ...` prefix in subject: bumps feature version, e.g. `1.2.3` → `1.3.0` +3. `BREAKING CHANGE:` in body: bumps breaking version, e.g. `1.2.3` → `2.0.0` + +Only one version number is bumped at a time, the highest version change trumps the others. +Besides, publishing a new version to npm, semantic-release also creates a git tag and release +on GitHub, generates changelogs from the commit messages and puts them into the release notes. + +If the pull request looks good but does not follow the commit conventions, update the pull request title and use the Squash & merge button, at which point you can set a custom commit message. diff --git a/README.md b/README.md index e7b8527..9c9f6ae 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,27 @@ const payloadIsVerified = await verifyRequestByKeyId( // true or false ``` +### Build a response + +```js +import { createAckEvent, createDoneEvent, createTextEvent } from "@copilot-extensions/preview-sdk"; + +export default handler(request, response) { + const ackEvent = createAckEvent(); + const textEvent = createTextEvent("Hello, world!"); + const doneEvent = createDoneEvent(); + + response.write(ackEvent.toString()); + response.write(textEvent.toString()); + response.end(doneEvent.toString()); +} +``` + ## API -### `async verifyRequestByKeyId(rawBody, signature, keyId, options)` +### Verification + +#### `async verifyRequestByKeyId(rawBody, signature, keyId, options)` 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. @@ -46,7 +64,7 @@ await verifyRequestByKeyId(request.body, signature, key, { token: "ghp_1234" }); await verifyRequestByKeyId(request.body, signature, key, { request }); ``` -### `async fetchVerificationKeys(options)` +#### `async fetchVerificationKeys(options)` Fetches public keys for verifying copilot extension requests [from GitHub's API](https://api.github.com/meta/public_keys/copilot_api) 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" }); const [current] = await fetchVerificationKeys({ request });) ``` -### `async verifyRequestPayload(rawBody, signature, keyId)` +#### `async verifyRequestPayload(rawBody, signature, keyId)` 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. @@ -79,6 +97,111 @@ const payloadIsVerified = await verifyRequestPayload( // true or false ``` +### Response + +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. + +#### `createAckEvent()` + +Acknowledge the request so that the chat UI can tell the user that the agent started generating a response. +The `ack` event should only be sent once. + +```js +import { createAckEvent } from "@copilot-extensions/preview-sdk"; + +response.write(createAckEvent().toString()); +``` + +#### `createTextEvent(message)` + +Send a text message to the chat UI. Multiple messages can be sent. The `message` argument must be a string and may include markdown. + +```js +import { createTextEvent } from "@copilot-extensions/preview-sdk"; + +response.write(createTextEvent("Hello, world!").toString()); +response.write(createTextEvent("Hello, again!").toString()); +``` + +#### `createConfirmationEvent({ id, title, message, metadata })` + +Ask the user to confirm an action. The `confirmation` event should only be sent once. + +The `meta` data object will be sent along the user's response. + +See additional documentation about Copilot confirmations at https://github.com/github/copilot-partners/blob/main/docs/confirmations.md. + +```js +import { createConfirmationEvent } from "@copilot-extensions/preview-sdk"; + +response.write( + createConfirmationEvent({ + id: "123", + title: "Are you sure?", + message: "This will do something.", + }).toString() +); +``` + +#### `createReferencesEvent(references)` + +Send a list of references to the chat UI. The `references` argument must be an array of objects with the following properties: + +- `id` +- `type` + +The following properties are optional + +- `data`: object with any properties. +- `is_implicit`: a boolean +- `metadata`: an object with a required `display_name` and the optional properties: `display_icon` and `display_url` + +Multiple `references` events can be sent. + +See additional documentation about Copilot references at https://github.com/github/copilot-partners/blob/main/docs/copilot-references.md. + +```js +import { createReferencesEvent } from "@copilot-extensions/preview-sdk"; + +response.write( + createReferencesEvent([ + { + id: "123", + type: "issue", + data: { + number: 123, + }, + is_implicit: false, + metadata: { + display_name: "My issue", + display_icon: "issue-opened", + display_url: "https://github.com/monalisa/hello-world/issues/123", + }, + ]).toString() +); +``` + +#### `createErrorsEvent(errors)` + +An array of objects with the following properties: + +- `type`: must be one of: `"reference"`, `"function"`, `"agent"` +- `code` +- `message` +- `identifier` + +See additional documentation about Copilot errors at https://github.com/github/copilot-partners/blob/main/docs/copilot-errors.md. + +#### `createDoneEvent()` + +The `done` event should only be sent once, at the end of the response. No further events can be sent after the `done` event. + +```js +import { createDoneEvent } from "@copilot-extensions/preview-sdk"; + +response.write(createDoneEvent().toString()); +``` + ## Dreamcode 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: diff --git a/index.d.ts b/index.d.ts index ba08dcc..0f25568 100644 --- a/index.d.ts +++ b/index.d.ts @@ -34,6 +34,105 @@ interface VerifyRequestByKeyIdInterface { ): Promise; } +export interface CreateAckEventInterface { + (): ResponseEvent<"ack"> +} + +export interface CreateTextEventInterface { + (message: string): ResponseEvent<"text"> +} + +export type CreateConfirmationEventOptions = { id: string, title: string, message: string, metadata?: Record } + +export interface CreateConfirmationEventInterface { + (options: CreateConfirmationEventOptions): ResponseEvent<"copilot_confirmation"> +} +export interface CreateReferencesEventInterface { + (references: CopilotReference[]): ResponseEvent<"copilot_references"> +} +export interface CreateErrorsEventInterface { + (errors: CopilotError[]): ResponseEvent<"copilot_errors"> +} +export interface CreateDoneEventInterface { + (): ResponseEvent<"done"> +} + +type ResponseEventType = "ack" | "done" | "text" | "copilot_references" | "copilot_confirmation" | "copilot_errors" +type EventsWithoutEventKey = "ack" | "done" | "text" +type ResponseEvent = + 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: { + content: "", role: "assistant" + } + }] +} + +type CopilotDoneResponseEventData = { + choices: [{ + finish_reason: "stop" + delta: { + content: null + } + }] +} + +type CopilotTextResponseEventData = { + choices: [{ + delta: { + content: string, role: "assistant" + } + }] +} +type CopilotConfirmationResponseEventData = { + type: 'action'; + title: string; + message: string; + confirmation?: { + id: string; + [key: string]: any; + }; +} +type CopilotErrorsResponseEventData = CopilotError[] +type CopilotReferenceResponseEventData = CopilotReference[] + +type CopilotError = { + type: "reference" | "function" | "agent"; + code: string; + message: string; + identifier: string; +} + +interface CopilotReference { + type: string; + id: string; + data?: { + [key: string]: unknown; + }; + is_implicit?: boolean; + metadata?: { + display_name: string; + display_icon?: string; + display_url?: string; + }; +} + export declare const verifyRequest: VerifyRequestInterface; export declare const fetchVerificationKeys: FetchVerificationKeysInterface; export declare const verifyRequestByKeyId: VerifyRequestByKeyIdInterface; + +export declare const createAckEvent: CreateAckEventInterface; +export declare const createConfirmationEvent: CreateConfirmationEventInterface; +export declare const createDoneEvent: CreateDoneEventInterface; +export declare const createErrorsEvent: CreateErrorsEventInterface; +export declare const createReferencesEvent: CreateReferencesEventInterface; +export declare const createTextEvent: CreateTextEventInterface; diff --git a/index.js b/index.js index 9d1c8dc..686156a 100644 --- a/index.js +++ b/index.js @@ -1,78 +1,4 @@ // @ts-check -import { createVerify } from "node:crypto"; - -import { request as defaultRequest } from "@octokit/request"; -import { RequestError } from "@octokit/request-error"; - -/** @type {import('.').VerifyRequestByKeyIdInterface} */ -export async function verifyRequest(rawBody, signature, key) { - // verify arguments - assertValidString(rawBody, "Invalid payload"); - assertValidString(signature, "Invalid signature"); - assertValidString(key, "Invalid key"); - - // verify signature - try { - return createVerify("SHA256") - .update(rawBody) - .verify(key, signature, "base64"); - } catch { - return false; - } -} - -/** @type {import('.').FetchVerificationKeysInterface} */ -export async function fetchVerificationKeys( - { token = "", request = defaultRequest } = { request: defaultRequest } -) { - const { data } = await request("GET /meta/public_keys/copilot_api", { - headers: token - ? { - Authorization: `token ${token}`, - } - : {}, - }); - - return data.public_keys; -} - -/** @type {import('.').VerifyRequestByKeyIdInterface} */ -export async function verifyRequestByKeyId( - rawBody, - signature, - keyId, - requestOptions -) { - // verify arguments - assertValidString(rawBody, "Invalid payload"); - assertValidString(signature, "Invalid signature"); - assertValidString(keyId, "Invalid keyId"); - - // receive valid public keys from GitHub - const keys = await fetchVerificationKeys(requestOptions); - - // verify provided key Id - const publicKey = keys.find((key) => key.key_identifier === keyId); - - if (!publicKey) { - const keyNotFoundError = Object.assign( - new Error( - "[@copilot-extensions/preview-sdk] No public key found matching key identifier" - ), - { - keyId, - keys, - } - ); - throw keyNotFoundError; - } - - return verifyRequest(rawBody, signature, publicKey.key); -} - -function assertValidString(value, message) { - if (typeof value !== "string" || value.length === 0) { - throw new Error(`[@copilot-extensions/preview-sdk] ${message}`); - } -} +export * from "./lib/verification.js"; +export * from "./lib/response.js"; diff --git a/index.test-d.ts b/index.test-d.ts index 3596d63..14413b7 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -2,6 +2,12 @@ import { expectType } from "tsd"; import { request } from "@octokit/request"; import { + createAckEvent, + createConfirmationEvent, + createDoneEvent, + createErrorsEvent, + createReferencesEvent, + createTextEvent, fetchVerificationKeys, verifyRequest, verifyRequestByKeyId, @@ -63,4 +69,161 @@ export async function fetchVerificationKeysTest() { // accepts a request argument await fetchVerificationKeys({ request }); -} \ No newline at end of file +} + +export function createAckEventTest() { + const event = createAckEvent(); + expectType<() => string>(event.toString); + expectType(event.toString()); + + expectType<{ + choices: [{ + delta: { + content: "", role: "assistant" + } + }] + }>(event.data); + + // @ts-expect-error - .event is required + event.event +} + +export function createTextEventTest() { + const event = createTextEvent("test"); + expectType<() => string>(event.toString); + expectType(event.toString()); + + expectType<{ + choices: [{ + delta: { + content: string, role: "assistant" + } + }] + }>(event.data); + + // @ts-expect-error - .event is required + event.event +} + +export function createConfirmationEventTest() { + const event = createConfirmationEvent({ + id: "test", + title: "test", + message: "test" + }); + + // optional metadata + createConfirmationEvent({ + id: "test", + title: "test", + message: "test", + metadata: { + someOtherId: "test", + } + }) + + expectType<() => string>(event.toString); + expectType(event.toString()); + + expectType<{ + type: 'action'; + title: string; + message: string; + confirmation?: { + id: string; + [key: string]: any; + }; + }>(event.data); + + expectType<"copilot_confirmation">(event.event); +} + +export function createReferencesEventTest() { + const event = createReferencesEvent([ + { + type: "test.story", + id: "test", + data: { + file: "test.js", + start: "1", + end: "42", + content: "function test() {...}", + }, + is_implicit: false, + metadata: { + display_name: "Lines 1-42 from test.js", + display_icon: "test-icon", + display_url: + "http://github.com/monalisa/hello-world/blob/main/test.js#L1-L42", + }, + }, + ]); + expectType<() => string>(event.toString); + expectType(event.toString()); + + 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<"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", + }]); + expectType<() => string>(event.toString); + expectType(event.toString()); + + expectType<{ + type: "reference" | "function" | "agent"; + code: string; + message: string; + identifier: string; + }[]>(event.data); + + expectType<"copilot_errors">(event.event); +} + +export function createDoneEventTest() { + const event = createDoneEvent(); + expectType<() => string>(event.toString); + expectType(event.toString()); + + expectType<{ + "choices": [ + { + "finish_reason": "stop", + "delta": { + "content": null + } + } + ] + }>(event.data); + + // @ts-expect-error - .event is required + event.event +} diff --git a/lib/response.js b/lib/response.js new file mode 100644 index 0000000..d1344b3 --- /dev/null +++ b/lib/response.js @@ -0,0 +1,88 @@ +// @ts-check + +/** @type {import('..').CreateAckEventInterface} */ +export function createAckEvent() { + return { + data: { + choices: [ + { + delta: { content: ``, role: "assistant" }, + }, + ], + }, + toString() { + return `data: ${JSON.stringify(this.data)}\n\n`; + }, + }; +} + +/** @type {import('..').CreateTextEventInterface} */ +export function createTextEvent(message) { + return { + data: { + choices: [ + { + delta: { content: message, role: "assistant" }, + }, + ], + }, + toString() { + return `data: ${JSON.stringify(this.data)}\n\n`; + }, + }; +} + +/** @type {import('..').CreateConfirmationEventInterface} */ +export function createConfirmationEvent({ id, title, message, metadata }) { + return { + event: "copilot_confirmation", + data: { + type: "action", + title, + message, + confirmation: { id, ...metadata }, + }, + toString() { + return `event: ${this.event}\ndata: ${JSON.stringify(this.data)}\n\n`; + }, + }; +} + +/** @type {import('..').CreateReferencesEventInterface} */ +export function createReferencesEvent(references) { + return { + event: "copilot_references", + data: references, + toString() { + return `event: ${this.event}\ndata: ${JSON.stringify(this.data)}\n\n`; + }, + }; +} + +/** @type {import('..').CreateErrorsEventInterface} */ +export function createErrorsEvent(errors) { + return { + event: "copilot_errors", + data: errors, + toString() { + return `event: ${this.event}\ndata: ${JSON.stringify(this.data)}\n\n`; + }, + }; +} + +/** @type {import('..').CreateDoneEventInterface} */ +export function createDoneEvent() { + return { + data: { + choices: [ + { + finish_reason: "stop", + delta: { content: null }, + }, + ], + }, + toString() { + return `data: ${JSON.stringify(this.data)}\n\ndata: [DONE]\n\n`; + }, + }; +} diff --git a/lib/verification.js b/lib/verification.js new file mode 100644 index 0000000..63e0ca6 --- /dev/null +++ b/lib/verification.js @@ -0,0 +1,77 @@ +// @ts-check + +import { createVerify } from "node:crypto"; + +import { request as defaultRequest } from "@octokit/request"; + +/** @type {import('..').VerifyRequestByKeyIdInterface} */ +export async function verifyRequest(rawBody, signature, key) { + // verify arguments + assertValidString(rawBody, "Invalid payload"); + assertValidString(signature, "Invalid signature"); + assertValidString(key, "Invalid key"); + + // verify signature + try { + return createVerify("SHA256") + .update(rawBody) + .verify(key, signature, "base64"); + } catch { + return false; + } +} + +/** @type {import('..').FetchVerificationKeysInterface} */ +export async function fetchVerificationKeys( + { token = "", request = defaultRequest } = { request: defaultRequest } +) { + const { data } = await request("GET /meta/public_keys/copilot_api", { + headers: token + ? { + Authorization: `token ${token}`, + } + : {}, + }); + + return data.public_keys; +} + +/** @type {import('..').VerifyRequestByKeyIdInterface} */ +export async function verifyRequestByKeyId( + rawBody, + signature, + keyId, + requestOptions +) { + // verify arguments + assertValidString(rawBody, "Invalid payload"); + assertValidString(signature, "Invalid signature"); + assertValidString(keyId, "Invalid keyId"); + + // receive valid public keys from GitHub + const keys = await fetchVerificationKeys(requestOptions); + + // verify provided key Id + const publicKey = keys.find((key) => key.key_identifier === keyId); + + if (!publicKey) { + const keyNotFoundError = Object.assign( + new Error( + "[@copilot-extensions/preview-sdk] No public key found matching key identifier" + ), + { + keyId, + keys, + } + ); + throw keyNotFoundError; + } + + return verifyRequest(rawBody, signature, publicKey.key); +} + +function assertValidString(value, message) { + if (typeof value !== "string" || value.length === 0) { + throw new Error(`[@copilot-extensions/preview-sdk] ${message}`); + } +} diff --git a/package.json b/package.json index 480fd75..2dc99a7 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,9 @@ "@octokit/request-error": "^6.1.4" }, "scripts": { - "test": "npm run test:code && npm run test:types", - "test:code": "node --test --experimental-test-coverage", + "test": "npm run test:code && npm run test:tsc && npm run test:types", + "test:code": "node --test --experimental-test-coverage --experimental-test-snapshots", + "test:code:update-snapshots": "node --test --experimental-test-coverage --experimental-test-snapshots --test-update-snapshots", "test:tsc": "tsc --allowJs --noEmit --esModuleInterop --skipLibCheck --lib es2020 index.js", "test:types": "tsd", "lint": "prettier --check \"*.{js,json,ts,md}\" \".github/**/*.yml\" \"test/*.ts\"", diff --git a/test/response.test.js b/test/response.test.js new file mode 100644 index 0000000..3f1a660 --- /dev/null +++ b/test/response.test.js @@ -0,0 +1,99 @@ +// @ts-check + +import { test, suite } from "node:test"; + +import { + createAckEvent, + createConfirmationEvent, + createDoneEvent, + createErrorsEvent, + createReferencesEvent, + createTextEvent, +} from "../index.js"; + +suite("response", () => { + test("smoke", (t) => { + t.assert.equal(typeof createAckEvent, "function"); + }); + + test("createAckEvent()", (t) => { + const event = createAckEvent(); + t.assert.equal(undefined, event.event); + t.assert.snapshot(event.data); + t.assert.snapshot(event.toString()); + }); + + test("createDoneEvent()", (t) => { + const event = createDoneEvent(); + t.assert.equal(undefined, event.event); + t.assert.snapshot(event.data); + t.assert.snapshot(event.toString()); + }); + + test("createConfirmationEvent()", (t) => { + const event = createConfirmationEvent({ + id: "123", + title: "title", + message: "message", + metadata: { foo: "bar" }, + }); + t.assert.equal("copilot_confirmation", event.event); + t.assert.snapshot(event.data); + t.assert.snapshot(event.toString()); + }); + + test("createErrorsEvent()", (t) => { + const referenceError = { + type: "reference", + code: "1", + message: "test reference error", + identifier: "reference-identifier", + }; + const functionError = { + type: "function", + code: "1", + message: "test function error", + identifier: "function-identifier", + }; + const agentError = { + type: "agent", + code: "1", + message: "test agent error", + identifier: "agent-identifier", + }; + const event = createErrorsEvent([ + referenceError, + functionError, + agentError, + ]); + t.assert.equal("copilot_errors", event.event); + t.assert.snapshot(event.data); + t.assert.snapshot(event.toString()); + }); + + test("createReferencesEvent()", (t) => { + const event = createReferencesEvent([ + { + type: "test.story", + id: "test", + data: { + file: "test.js", + start: "1", + end: "42", + content: "function test() {...}", + }, + is_implicit: false, + metadata: { + display_name: "Lines 1-42 from test.js", + display_icon: "test-icon", + display_url: + "http://github.com/monalisa/hello-world/blob/main/test.js#L1-L42", + }, + }, + ]); + + t.assert.equal("copilot_references", event.event); + t.assert.snapshot(event.data); + t.assert.snapshot(event.toString()); + }); +}); diff --git a/test/response.test.js.snapshot b/test/response.test.js.snapshot new file mode 100644 index 0000000..0005d55 --- /dev/null +++ b/test/response.test.js.snapshot @@ -0,0 +1,101 @@ +exports[`response > createAckEvent() 1`] = ` +{ + "choices": [ + { + "delta": { + "content": "", + "role": "assistant" + } + } + ] +} +`; + +exports[`response > createAckEvent() 2`] = ` +"data: {\\"choices\\":[{\\"delta\\":{\\"content\\":\\"\\",\\"role\\":\\"assistant\\"}}]}\\n\\n" +`; + +exports[`response > createConfirmationEvent() 1`] = ` +{ + "type": "action", + "title": "title", + "message": "message", + "confirmation": { + "id": "123", + "foo": "bar" + } +} +`; + +exports[`response > createConfirmationEvent() 2`] = ` +"event: copilot_confirmation\\ndata: {\\"type\\":\\"action\\",\\"title\\":\\"title\\",\\"message\\":\\"message\\",\\"confirmation\\":{\\"id\\":\\"123\\",\\"foo\\":\\"bar\\"}}\\n\\n" +`; + +exports[`response > createDoneEvent() 1`] = ` +{ + "choices": [ + { + "finish_reason": "stop", + "delta": { + "content": null + } + } + ] +} +`; + +exports[`response > createDoneEvent() 2`] = ` +"data: {\\"choices\\":[{\\"finish_reason\\":\\"stop\\",\\"delta\\":{\\"content\\":null}}]}\\n\\ndata: [DONE]\\n\\n" +`; + +exports[`response > createErrorsEvent() 1`] = ` +[ + { + "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" + } +] +`; + +exports[`response > createErrorsEvent() 2`] = ` +"event: copilot_errors\\ndata: [{\\"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\\"}]\\n\\n" +`; + +exports[`response > createReferencesEvent() 1`] = ` +[ + { + "type": "test.story", + "id": "test", + "data": { + "file": "test.js", + "start": "1", + "end": "42", + "content": "function test() {...}" + }, + "is_implicit": false, + "metadata": { + "display_name": "Lines 1-42 from test.js", + "display_icon": "test-icon", + "display_url": "http://github.com/monalisa/hello-world/blob/main/test.js#L1-L42" + } + } +] +`; + +exports[`response > createReferencesEvent() 2`] = ` +"event: copilot_references\\ndata: [{\\"type\\":\\"test.story\\",\\"id\\":\\"test\\",\\"data\\":{\\"file\\":\\"test.js\\",\\"start\\":\\"1\\",\\"end\\":\\"42\\",\\"content\\":\\"function test() {...}\\"},\\"is_implicit\\":false,\\"metadata\\":{\\"display_name\\":\\"Lines 1-42 from test.js\\",\\"display_icon\\":\\"test-icon\\",\\"display_url\\":\\"http://github.com/monalisa/hello-world/blob/main/test.js#L1-L42\\"}}]\\n\\n" +`; diff --git a/test/integration.test.js b/test/verification.test.js similarity index 100% rename from test/integration.test.js rename to test/verification.test.js