From aaf5efc01bd91023ee79254938f848519b8d1dee Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Tue, 6 Dec 2022 16:13:11 -0600 Subject: [PATCH] feat: WIP add support for native fetch; undici on node.js < 18, and refactor conversation support --- package.json | 10 ++-- pnpm-lock.yaml | 102 +++++++++------------------------ src/chatgpt-api.test.ts | 2 +- src/chatgpt-api.ts | 106 ++++++++--------------------------- src/chatgpt-conversation.ts | 71 +++++++++++++++++++++++ src/demo-conversation.ts | 4 +- src/demo.ts | 2 +- src/fetch-sse.ts | 19 ++----- src/fetch.ts | 10 +++- src/index.ts | 1 + src/stream-async-iterable.ts | 4 +- tsup.config.ts | 40 +++++++++---- 12 files changed, 174 insertions(+), 197 deletions(-) create mode 100644 src/chatgpt-conversation.ts diff --git a/package.json b/package.json index aac5024..d27c553 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,14 @@ "build" ], "engines": { - "node": ">=14" + "node": ">=16.8" }, "scripts": { "build": "tsup", "dev": "tsup --watch", "clean": "del build", "prebuild": "run-s clean", + "postbuild": "sed -i 's/ *\\?\\? *(await import(\"undici\")).fetch//' build/browser/index.js", "predev": "run-s clean", "pretest": "run-s build", "docs": "typedoc", @@ -38,7 +39,6 @@ "dependencies": { "eventsource-parser": "^0.0.5", "expiry-map": "^2.0.0", - "node-fetch": "2", "remark": "^14.0.2", "strip-markdown": "^5.0.0", "uuid": "^9.0.0" @@ -46,7 +46,6 @@ "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.0.0", "@types/node": "^18.11.9", - "@types/node-fetch": "2", "@types/uuid": "^9.0.0", "ava": "^5.1.0", "del-cli": "^5.0.0", @@ -89,5 +88,8 @@ "ai", "ml", "bot" - ] + ], + "optionalDependencies": { + "undici": "^5.13.0" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24fc4dc..4ddaac7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,7 +3,6 @@ lockfileVersion: 5.4 specifiers: '@trivago/prettier-plugin-sort-imports': ^4.0.0 '@types/node': ^18.11.9 - '@types/node-fetch': '2' '@types/uuid': ^9.0.0 ava: ^5.1.0 del-cli: ^5.0.0 @@ -12,7 +11,6 @@ specifiers: expiry-map: ^2.0.0 husky: ^8.0.2 lint-staged: ^13.0.3 - node-fetch: '2' npm-run-all: ^4.1.5 ora: ^6.1.2 prettier: ^2.8.0 @@ -23,20 +21,22 @@ specifiers: typedoc: ^0.23.21 typedoc-plugin-markdown: ^3.13.6 typescript: ^4.9.3 + undici: ^5.13.0 uuid: ^9.0.0 dependencies: eventsource-parser: 0.0.5 expiry-map: 2.0.0 - node-fetch: 2.6.7 remark: 14.0.2 strip-markdown: 5.0.0 uuid: 9.0.0 +optionalDependencies: + undici: 5.13.0 + devDependencies: '@trivago/prettier-plugin-sort-imports': 4.0.0_prettier@2.8.0 '@types/node': 18.11.10 - '@types/node-fetch': 2.6.2 '@types/uuid': 9.0.0 ava: 5.1.0 del-cli: 5.0.0 @@ -434,13 +434,6 @@ packages: resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} dev: false - /@types/node-fetch/2.6.2: - resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==} - dependencies: - '@types/node': 18.11.10 - form-data: 3.0.1 - dev: true - /@types/node/18.11.10: resolution: {integrity: sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==} dev: true @@ -568,10 +561,6 @@ packages: engines: {node: '>=8'} dev: true - /asynckit/0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: true - /ava/5.1.0: resolution: {integrity: sha512-e5VFrSQ0WBPyZJWRXVrO7RFOizFeNM0t2PORwrPvWtApgkORI6cvGnY3GX1G+lzpd0HjqNx5Jus22AhxVnUMNA==} engines: {node: '>=14.19 <15 || >=16.15 <17 || >=18'} @@ -712,6 +701,14 @@ packages: load-tsconfig: 0.2.3 dev: true + /busboy/1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + dependencies: + streamsearch: 1.1.0 + dev: false + optional: true + /cac/6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -899,13 +896,6 @@ packages: resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} dev: true - /combined-stream/1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - dependencies: - delayed-stream: 1.0.0 - dev: true - /commander/4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1053,11 +1043,6 @@ packages: slash: 4.0.0 dev: true - /delayed-stream/1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - dev: true - /dequal/2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1491,15 +1476,6 @@ packages: path-exists: 5.0.0 dev: true - /form-data/3.0.1: - resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} - engines: {node: '>= 6'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - dev: true - /fs.realpath/1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true @@ -2426,18 +2402,6 @@ packages: picomatch: 2.3.1 dev: true - /mime-db/1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - dev: true - - /mime-types/2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - dependencies: - mime-db: 1.52.0 - dev: true - /mimic-fn/2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -2507,18 +2471,6 @@ packages: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} dev: true - /node-fetch/2.6.7: - resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - dependencies: - whatwg-url: 5.0.0 - dev: false - /node-releases/2.0.6: resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} dev: true @@ -3210,6 +3162,12 @@ packages: escape-string-regexp: 2.0.0 dev: true + /streamsearch/1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + dev: false + optional: true + /string-argv/0.3.1: resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} engines: {node: '>=0.6.19'} @@ -3382,10 +3340,6 @@ packages: is-number: 7.0.0 dev: true - /tr46/0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - dev: false - /tr46/1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} dependencies: @@ -3522,6 +3476,15 @@ packages: which-boxed-primitive: 1.0.2 dev: true + /undici/5.13.0: + resolution: {integrity: sha512-UDZKtwb2k7KRsK4SdXWG7ErXiL7yTGgLWvk2AXO1JMjgjh404nFo6tWSCM2xMpJwMPx3J8i/vfqEh1zOqvj82Q==} + engines: {node: '>=12.18'} + requiresBuild: true + dependencies: + busboy: 1.6.0 + dev: false + optional: true + /unified/10.1.2: resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} dependencies: @@ -3627,10 +3590,6 @@ packages: defaults: 1.0.4 dev: true - /webidl-conversions/3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - dev: false - /webidl-conversions/4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} dev: true @@ -3640,13 +3599,6 @@ packages: engines: {node: '>=6'} dev: true - /whatwg-url/5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - dev: false - /whatwg-url/7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} dependencies: diff --git a/src/chatgpt-api.test.ts b/src/chatgpt-api.test.ts index ea0bd18..090afba 100644 --- a/src/chatgpt-api.test.ts +++ b/src/chatgpt-api.test.ts @@ -63,7 +63,7 @@ if (!isCI) { }, { message: - 'ChatGPT failed to refresh auth token. Error: session token has expired' + 'ChatGPT failed to refresh auth token. Error: session token may have expired' } ) }) diff --git a/src/chatgpt-api.ts b/src/chatgpt-api.ts index 32a39a1..6db89bf 100644 --- a/src/chatgpt-api.ts +++ b/src/chatgpt-api.ts @@ -2,6 +2,7 @@ import ExpiryMap from 'expiry-map' import { v4 as uuidv4 } from 'uuid' import * as types from './types' +import { ChatGPTConversation } from './chatgpt-conversation' import { fetch } from './fetch' import { fetchSSE } from './fetch-sse' import { markdownToText } from './utils' @@ -10,80 +11,10 @@ const KEY_ACCESS_TOKEN = 'accessToken' const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' -/** - * A conversation wrapper around the ChatGPT API. This allows you to send - * multiple messages to ChatGPT and receive responses, without having to - * manually pass the conversation ID and parent message ID for each message. - */ -class Conversation { - api: ChatGPTAPI - conversationId: string = undefined - parentMessageId: string = undefined - - /** - * Creates a new conversation wrapper around the ChatGPT API. - * @param api - The ChatGPT API instance to use. - */ - constructor( - api: ChatGPTAPI, - opts: { conversationId?: string; parentMessageId?: string } = {} - ) { - this.api = api - this.conversationId = opts.conversationId - this.parentMessageId = opts.parentMessageId - } - - /** - * Sends a message to ChatGPT, waits for the response to resolve, and returns - * the response. - * If this is the first message in the conversation, the conversation ID and - * parent message ID will be automatically set. - * This allows you to send multiple messages to ChatGPT and receive responses, - * without having to manually pass the conversation ID and parent message ID - * for each message. - * If you want to manually pass the conversation ID and parent message ID, - * use `api.sendMessage` instead. - * - * @param message - The plaintext message to send. - * @param opts.onProgress - Optional listener which will be called every time the partial response is updated - * @param opts.onConversationResponse - Optional listener which will be called every time a conversation response is received - * @returns The plaintext response from ChatGPT. - */ - async sendMessage( - message: string, - opts: { - onProgress?: (partialResponse: string) => void - onConversationResponse?: ( - response: types.ConversationResponseEvent - ) => void - } = {} - ) { - const { onProgress, onConversationResponse } = opts - if (!this.conversationId) { - return this.api.sendMessage(message, { - onProgress, - onConversationResponse: (response) => { - this.conversationId = response.conversation_id - this.parentMessageId = response.message.id - onConversationResponse?.(response) - } - }) - } - - return this.api.sendMessage(message, { - conversationId: this.conversationId, - parentMessageId: this.parentMessageId, - onProgress, - onConversationResponse: (response) => { - this.conversationId = response.conversation_id - this.parentMessageId = response.message.id - onConversationResponse?.(response) - } - }) - } -} - export class ChatGPTAPI { + public conversationId: string = undefined + public parentMessageId: string = undefined + protected _sessionToken: string protected _markdown: boolean protected _apiBaseUrl: string @@ -152,10 +83,13 @@ export class ChatGPTAPI { * Sends a message to ChatGPT, waits for the response to resolve, and returns * the response. * - * @param message - The plaintext message to send. - * @param opts.conversationId - Optional ID of the previous message in a conversation - * @param opts.onProgress - Optional listener which will be called every time the partial response is updated - * @param opts.onConversationResponse - Optional listener which will be called every time the partial response is updated with the full conversation response + * @param message - The prompt message to send + * @param opts.conversationId - Optional ID of a conversation to continue + * @param opts.parentMessageId - Optional ID of the previous message in the conversation + * @param opts.onProgress - Optional function which will be called every time the partial response is updated + * @param opts.onConversationResponse - Optional function which will be called every time the partial response is updated with the full conversation response + * @param opts.abortSignal - Optional function used to abort the underlying `fetch` call using an [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) + * @returns The response from ChatGPT */ async sendMessage( message: string, @@ -166,13 +100,15 @@ export class ChatGPTAPI { onConversationResponse?: ( response: types.ConversationResponseEvent ) => void + abortSignal?: AbortSignal } = {} ): Promise { const { conversationId, parentMessageId = uuidv4(), onProgress, - onConversationResponse + onConversationResponse, + abortSignal } = opts const accessToken = await this.refreshAccessToken() @@ -214,6 +150,7 @@ export class ChatGPTAPI { 'user-agent': this._userAgent }, body: JSON.stringify(body), + signal: abortSignal, onMessage: (data: string) => { if (data === '[DONE]') { return resolve(response) @@ -275,7 +212,7 @@ export class ChatGPTAPI { const error = res?.error if (error) { if (error === 'RefreshAccessTokenError') { - throw new Error('session token has expired') + throw new Error('session token may have expired') } else { throw new Error(error) } @@ -289,15 +226,16 @@ export class ChatGPTAPI { } /** - * Get a new Conversation instance, which can be used to send multiple messages as part of a single conversation. + * Gets a new ChatGPTConversation instance, which can be used to send multiple + * messages as part of a single conversation. * - * @param opts.conversationId - Optional Data of the previous message in a conversation - * @param opts.parentMessageId - Optional Data of the previous message in a conversation - * @returns a new Conversation instance + * @param opts.conversationId - Optional ID of the previous message in a conversation + * @param opts.parentMessageId - Optional ID of the previous message in a conversation + * @returns The new conversation instance */ getConversation( opts: { conversationId?: string; parentMessageId?: string } = {} ) { - return new Conversation(this, opts) + return new ChatGPTConversation(this, opts) } } diff --git a/src/chatgpt-conversation.ts b/src/chatgpt-conversation.ts new file mode 100644 index 0000000..f84cae2 --- /dev/null +++ b/src/chatgpt-conversation.ts @@ -0,0 +1,71 @@ +import * as types from './types' +import { type ChatGPTAPI } from './chatgpt-api' + +/** + * A conversation wrapper around the ChatGPTAPI. This allows you to send + * multiple messages to ChatGPT and receive responses, without having to + * manually pass the conversation ID and parent message ID for each message. + */ +export class ChatGPTConversation { + api: ChatGPTAPI + conversationId: string = undefined + parentMessageId: string = undefined + + /** + * Creates a new conversation wrapper around the ChatGPT API. + * + * @param api - The ChatGPT API instance to use + * @param opts.conversationId - Optional ID of a conversation to continue + * @param opts.parentMessageId - Optional ID of the previous message in the conversation + */ + constructor( + api: ChatGPTAPI, + opts: { conversationId?: string; parentMessageId?: string } = {} + ) { + this.api = api + this.conversationId = opts.conversationId + this.parentMessageId = opts.parentMessageId + } + + /** + * Sends a message to ChatGPT, waits for the response to resolve, and returns + * the response. + * + * If this is the first message in the conversation, the conversation ID and + * parent message ID will be automatically set. + * + * This allows you to send multiple messages to ChatGPT and receive responses, + * without having to manually pass the conversation ID and parent message ID + * for each message. + * + * @param message - The prompt message to send + * @param opts.onProgress - Optional listener which will be called every time the partial response is updated + * @param opts.onConversationResponse - Optional listener which will be called every time a conversation response is received + * @returns The response from ChatGPT + */ + async sendMessage( + message: string, + opts: { + onProgress?: (partialResponse: string) => void + onConversationResponse?: ( + response: types.ConversationResponseEvent + ) => void + } = {} + ): Promise { + const { onProgress, onConversationResponse } = opts + + return this.api.sendMessage(message, { + conversationId: this.conversationId, + parentMessageId: this.parentMessageId, + onProgress, + onConversationResponse: (response) => { + this.conversationId = response.conversation_id + this.parentMessageId = response.message.id + + if (onConversationResponse) { + return onConversationResponse(response) + } + } + }) + } +} diff --git a/src/demo-conversation.ts b/src/demo-conversation.ts index c0199b8..915f39b 100644 --- a/src/demo-conversation.ts +++ b/src/demo-conversation.ts @@ -6,10 +6,10 @@ import { ChatGPTAPI } from '.' dotenv.config() /** - * Example CLI for testing functionality. + * Demo CLI for testing conversation support. * * ``` - * npx tsx src/demo.ts + * npx tsx src/demo-conversation.ts * ``` */ async function main() { diff --git a/src/demo.ts b/src/demo.ts index 7bae4db..dcfd028 100644 --- a/src/demo.ts +++ b/src/demo.ts @@ -6,7 +6,7 @@ import { ChatGPTAPI } from '.' dotenv.config() /** - * Example CLI for testing functionality. + * Demo CLI for testing basic functionality. * * ``` * npx tsx src/demo.ts diff --git a/src/fetch-sse.ts b/src/fetch-sse.ts index 3a0b585..15f2a54 100644 --- a/src/fetch-sse.ts +++ b/src/fetch-sse.ts @@ -1,8 +1,7 @@ import { createParser } from 'eventsource-parser' import { fetch } from './fetch' - -// import { streamAsyncIterable } from './stream-async-iterable' +import { streamAsyncIterable } from './stream-async-iterable' export async function fetchSSE( url: string, @@ -16,16 +15,8 @@ export async function fetchSSE( } }) - resp.body.on('readable', () => { - let chunk: string | Buffer - while (null !== (chunk = resp.body.read())) { - parser.feed(chunk.toString()) - } - }) - - // TODO: add support for web-compatible `fetch` - // for await (const chunk of streamAsyncIterable(resp.body)) { - // const str = new TextDecoder().decode(chunk) - // parser.feed(str) - // } + for await (const chunk of streamAsyncIterable(resp.body)) { + const str = new TextDecoder().decode(chunk) + parser.feed(str) + } } diff --git a/src/fetch.ts b/src/fetch.ts index fe65965..308761c 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -1,3 +1,11 @@ -import fetch from 'node-fetch' +/// + +// Use `undici` for node.js 16 and 17 +// Use `fetch` for node.js >= 18 +// Use `fetch` for browsers +// Use `fetch` for all other environments +const fetch = + globalThis.fetch ?? + ((await import('undici')).fetch as unknown as typeof globalThis.fetch) export { fetch } diff --git a/src/index.ts b/src/index.ts index 451712e..ed6a4b5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ export * from './chatgpt-api' +export * from './chatgpt-conversation' export * from './types' export * from './utils' diff --git a/src/stream-async-iterable.ts b/src/stream-async-iterable.ts index fbfe174..78eb497 100644 --- a/src/stream-async-iterable.ts +++ b/src/stream-async-iterable.ts @@ -1,6 +1,4 @@ -import { type ReadableStream } from 'stream/web' - -export async function* streamAsyncIterable(stream: ReadableStream) { +export async function* streamAsyncIterable(stream: ReadableStream) { const reader = stream.getReader() try { while (true) { diff --git a/tsup.config.ts b/tsup.config.ts index 5dedfd7..321fadf 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,14 +1,30 @@ import { defineConfig } from 'tsup' -export default defineConfig({ - entry: ['src/index.ts'], - outDir: 'build', - target: 'node14', - platform: 'node', - format: ['esm'], - splitting: false, - sourcemap: true, - minify: true, - shims: false, - dts: true -}) +export default defineConfig([ + { + entry: ['src/index.ts'], + outDir: 'build', + target: 'node16', + platform: 'node', + format: ['esm'], + splitting: false, + sourcemap: true, + minify: false, + shims: true, + dts: true, + external: ['undici'] + }, + { + entry: ['src/index.ts'], + outDir: 'build/browser', + target: 'chrome89', + platform: 'browser', + format: ['esm'], + splitting: false, + sourcemap: true, + minify: false, + shims: true, + dts: true, + external: ['undici'] + } +])