From 43a5e4e6f462ef4c5e95a23fa71662e7cc5ee283 Mon Sep 17 00:00:00 2001 From: Gregor Martynus <39992+gr2m@users.noreply.github.com> Date: Thu, 5 Sep 2024 11:33:58 -0700 Subject: [PATCH] feat: `create*Event` methods now return strings (#59) BREAKING CHANGE: All `create*Event` methods now return a string instead of an object with a `.toString()` method. Before: ```js esponse.write(createTextEvent("Hello, world!").toString()); ``` Now: ```js esponse.write(createTextEvent("Hello, world!")); ``` --- README.md | 20 +++--- index.d.ts | 83 ++----------------------- index.test-d.ts | 108 +++------------------------------ lib/response.js | 94 +++++++++++----------------- test/response.test.js | 25 ++------ test/response.test.js.snapshot | 95 ----------------------------- 6 files changed, 64 insertions(+), 361 deletions(-) diff --git a/README.md b/README.md index cf51484..094fa4f 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,9 @@ export default handler(request, response) { const textEvent = createTextEvent("Hello, world!"); const doneEvent = createDoneEvent(); - response.write(ackEvent.toString()); - response.write(textEvent.toString()); - response.end(doneEvent.toString()); + response.write(ackEvent); + response.write(textEvent); + response.end(doneEvent); } ``` @@ -131,7 +131,7 @@ const payloadIsVerified = await verifyRequestPayload( ### 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. +All `create*Event()` methods return a string that can directly be written to the response stream. #### `createAckEvent()` @@ -141,7 +141,7 @@ The `ack` event should only be sent once. ```js import { createAckEvent } from "@copilot-extensions/preview-sdk"; -response.write(createAckEvent().toString()); +response.write(createAckEvent()); ``` #### `createTextEvent(message)` @@ -151,8 +151,8 @@ Send a text message to the chat UI. Multiple messages can be sent. The `message` ```js import { createTextEvent } from "@copilot-extensions/preview-sdk"; -response.write(createTextEvent("Hello, world!").toString()); -response.write(createTextEvent("Hello, again!").toString()); +response.write(createTextEvent("Hello, world!")); +response.write(createTextEvent("Hello, again!")); ``` #### `createConfirmationEvent({ id, title, message, metadata })` @@ -171,7 +171,7 @@ response.write( id: "123", title: "Are you sure?", message: "This will do something.", - }).toString(), + }), ); ``` @@ -209,7 +209,7 @@ response.write( display_icon: "issue-opened", display_url: "https://github.com/monalisa/hello-world/issues/123", }, - ]).toString() + ]) ); ``` @@ -231,7 +231,7 @@ The `done` event should only be sent once, at the end of the response. No furthe ```js import { createDoneEvent } from "@copilot-extensions/preview-sdk"; -response.write(createDoneEvent().toString()); +response.write(createDoneEvent()); ``` ### Parsing diff --git a/index.d.ts b/index.d.ts index 22b8870..24ae984 100644 --- a/index.d.ts +++ b/index.d.ts @@ -33,11 +33,10 @@ interface VerifyRequestByKeyIdInterface { // response types export interface CreateAckEventInterface { - (): ResponseEvent<"ack">; + (): string; } - export interface CreateTextEventInterface { - (message: string): ResponseEvent<"text">; + (message: string): string; } export type CreateConfirmationEventOptions = { @@ -50,88 +49,18 @@ export type CreateConfirmationEventOptions = { export interface CreateConfirmationEventInterface { ( options: CreateConfirmationEventOptions, - ): ResponseEvent<"copilot_confirmation">; + ): string; } export interface CreateReferencesEventInterface { - (references: CopilotReference[]): ResponseEvent<"copilot_references">; + (references: CopilotReference[]): string; } export interface CreateErrorsEventInterface { - (errors: CopilotError[]): ResponseEvent<"copilot_errors">; + (errors: CopilotError[]): string; } export interface CreateDoneEventInterface { - (): ResponseEvent<"done">; + (): string; } -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: InteropMessage<"assistant">; - }, - ]; -}; - -type CopilotDoneResponseEventData = { - choices: [ - { - finish_reason: "stop"; - delta: { - content: null; - }; - }, - ]; -}; - -type CopilotTextResponseEventData = { - choices: [ - { - delta: InteropMessage<"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; diff --git a/index.test-d.ts b/index.test-d.ts index 844b63a..0a41a4d 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -87,36 +87,12 @@ export async function fetchVerificationKeysTest() { export function createAckEventTest() { const event = createAckEvent(); - expectType<() => string>(event.toString); - expectType(event.toString()); - - expectType<{ - choices: [ - { - delta: InteropMessage<"assistant">; - }, - ]; - }>(event.data); - - // @ts-expect-error - .event is required - event.event; + expectType(event); } export function createTextEventTest() { const event = createTextEvent("test"); - expectType<() => string>(event.toString); - expectType(event.toString()); - - expectType<{ - choices: [ - { - delta: InteropMessage<"assistant">; - }, - ]; - }>(event.data); - - // @ts-expect-error - .event is required - event.event; + expectType(event); } export function createConfirmationEventTest() { @@ -125,31 +101,7 @@ export function createConfirmationEventTest() { 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); + expectType(event); } export function createReferencesEventTest() { @@ -172,26 +124,7 @@ export function createReferencesEventTest() { }, }, ]); - 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); + expectType(event); } export function createErrorsEventTest() { @@ -215,39 +148,12 @@ export function createErrorsEventTest() { 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); + expectType(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; + expectType(event); } export function parseRequestBodyTest(body: string) { @@ -312,7 +218,7 @@ export async function promptTest() { await prompt("What is the capital of France?", { token: "secret", request: { - fetch: () => {}, + fetch: () => { }, }, }); diff --git a/lib/response.js b/lib/response.js index d1344b3..caf226d 100644 --- a/lib/response.js +++ b/lib/response.js @@ -2,87 +2,63 @@ /** @type {import('..').CreateAckEventInterface} */ export function createAckEvent() { - return { - data: { - choices: [ - { - delta: { content: ``, role: "assistant" }, - }, - ], - }, - toString() { - return `data: ${JSON.stringify(this.data)}\n\n`; - }, + const data = { + choices: [ + { + delta: { content: ``, role: "assistant" }, + }, + ], }; + return `data: ${JSON.stringify(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`; - }, + const data = { + choices: [ + { + delta: { content: message, role: "assistant" }, + }, + ], }; + return `data: ${JSON.stringify(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`; - }, + const event = "copilot_confirmation"; + const data = { + type: "action", + title, + message, + confirmation: { id, ...metadata }, }; + return `event: ${event}\ndata: ${JSON.stringify(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`; - }, - }; + const event = "copilot_references"; + const data = references; + return `event: ${event}\ndata: ${JSON.stringify(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`; - }, - }; + const event = "copilot_errors"; + const data = errors; + return `event: ${event}\ndata: ${JSON.stringify(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`; - }, + const data = { + choices: [ + { + finish_reason: "stop", + delta: { content: null }, + }, + ], }; + return `data: ${JSON.stringify(data)}\n\ndata: [DONE]\n\n`; } diff --git a/test/response.test.js b/test/response.test.js index cd17199..b08ff00 100644 --- a/test/response.test.js +++ b/test/response.test.js @@ -16,23 +16,17 @@ suite("response", () => { test("createAckEvent()", (t) => { const event = createAckEvent(); - t.assert.equal(undefined, event.event); - t.assert.snapshot(event.data); - t.assert.snapshot(event.toString()); + t.assert.snapshot(event); }); test("createDoneEvent()", (t) => { const event = createDoneEvent(); - t.assert.equal(undefined, event.event); - t.assert.snapshot(event.data); - t.assert.snapshot(event.toString()); + t.assert.snapshot(event); }); test("createTextEvent()", (t) => { const event = createTextEvent("test"); - t.assert.equal(undefined, event.event); - t.assert.snapshot(event.data); - t.assert.snapshot(event.toString()); + t.assert.snapshot(event); }); test("createConfirmationEvent()", (t) => { @@ -42,9 +36,7 @@ suite("response", () => { message: "message", metadata: { foo: "bar" }, }); - t.assert.equal("copilot_confirmation", event.event); - t.assert.snapshot(event.data); - t.assert.snapshot(event.toString()); + t.assert.snapshot(event); }); test("createErrorsEvent()", (t) => { @@ -71,9 +63,7 @@ suite("response", () => { functionError, agentError, ]); - t.assert.equal("copilot_errors", event.event); - t.assert.snapshot(event.data); - t.assert.snapshot(event.toString()); + t.assert.snapshot(event); }); test("createReferencesEvent()", (t) => { @@ -96,9 +86,6 @@ suite("response", () => { }, }, ]); - - t.assert.equal("copilot_references", event.event); - t.assert.snapshot(event.data); - t.assert.snapshot(event.toString()); + t.assert.snapshot(event); }); }); diff --git a/test/response.test.js.snapshot b/test/response.test.js.snapshot index a5db3d5..ef81396 100644 --- a/test/response.test.js.snapshot +++ b/test/response.test.js.snapshot @@ -1,118 +1,23 @@ 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" `; exports[`response > createTextEvent() 1`] = ` -{ - "choices": [ - { - "delta": { - "content": "test", - "role": "assistant" - } - } - ] -} -`; - -exports[`response > createTextEvent() 2`] = ` "data: {\\"choices\\":[{\\"delta\\":{\\"content\\":\\"test\\",\\"role\\":\\"assistant\\"}}]}\\n\\n" `;