kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: WIP add support for native fetch; undici on node.js < 18, and refactor conversation support
rodzic
a1ac915a14
commit
aaf5efc01b
10
package.json
10
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"
|
||||
}
|
||||
}
|
||||
|
|
102
pnpm-lock.yaml
102
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:
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
)
|
||||
})
|
||||
|
|
|
@ -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<string> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<string> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
10
src/fetch.ts
10
src/fetch.ts
|
@ -1,3 +1,11 @@
|
|||
import fetch from 'node-fetch'
|
||||
/// <reference lib="dom" />
|
||||
|
||||
// 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 }
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from './chatgpt-api'
|
||||
export * from './chatgpt-conversation'
|
||||
export * from './types'
|
||||
export * from './utils'
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import { type ReadableStream } from 'stream/web'
|
||||
|
||||
export async function* streamAsyncIterable(stream: ReadableStream) {
|
||||
export async function* streamAsyncIterable<T>(stream: ReadableStream<T>) {
|
||||
const reader = stream.getReader()
|
||||
try {
|
||||
while (true) {
|
||||
|
|
|
@ -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']
|
||||
}
|
||||
])
|
||||
|
|
Ładowanie…
Reference in New Issue