|
|
@@ -5,85 +5,88 @@ import { fetch as globalFetch } from './fetch'
|
|
|
import { streamAsyncIterable } from './stream-async-iterable'
|
|
|
|
|
|
export async function fetchSSE(
|
|
|
- url: string,
|
|
|
- options: Parameters<typeof fetch>[1] & {
|
|
|
- onMessage: (data: string) => void
|
|
|
- onError?: (error: any) => void
|
|
|
- },
|
|
|
- fetch: types.FetchFn = globalFetch
|
|
|
+ url: string,
|
|
|
+ options: Parameters<typeof fetch>[1] & {
|
|
|
+ onMessage: (data: string) => void
|
|
|
+ onError?: (error: any) => void
|
|
|
+ },
|
|
|
+ fetch: types.FetchFn = globalFetch
|
|
|
) {
|
|
|
- const { onMessage, onError, ...fetchOptions } = options
|
|
|
- const res = await fetch(url, fetchOptions)
|
|
|
- if (!res.ok) {
|
|
|
- let reason: string
|
|
|
+ const { onMessage, onError, ...fetchOptions } = options
|
|
|
+ const res = await fetch(url, fetchOptions)
|
|
|
+ if (!res.ok) {
|
|
|
+ let reason: string
|
|
|
|
|
|
- try {
|
|
|
- reason = await res.text()
|
|
|
- } catch (err) {
|
|
|
- reason = res.statusText
|
|
|
+ try {
|
|
|
+ reason = await res.text()
|
|
|
+ } catch (err) {
|
|
|
+ reason = res.statusText
|
|
|
+ }
|
|
|
+
|
|
|
+ const msg = `ChatGPT error ${res.status}: ${reason}`
|
|
|
+ const error = new types.ChatGPTError(msg, { cause: res })
|
|
|
+ error.statusCode = res.status
|
|
|
+ error.statusText = res.statusText
|
|
|
+ throw error
|
|
|
}
|
|
|
|
|
|
- const msg = `ChatGPT error ${res.status}: ${reason}`
|
|
|
- const error = new types.ChatGPTError(msg, { cause: res })
|
|
|
- error.statusCode = res.status
|
|
|
- error.statusText = res.statusText
|
|
|
- throw error
|
|
|
- }
|
|
|
+ const parser = createParser((event) => {
|
|
|
+ if (event.type === 'event') {
|
|
|
+ onMessage(event.data)
|
|
|
+ }
|
|
|
+ })
|
|
|
|
|
|
- const parser = createParser((event) => {
|
|
|
- if (event.type === 'event') {
|
|
|
- onMessage(event.data)
|
|
|
- }
|
|
|
- })
|
|
|
+ // handle special response errors
|
|
|
+ const feed = (chunk: string) => {
|
|
|
+ let response = null
|
|
|
|
|
|
- // handle special response errors
|
|
|
- const feed = (chunk: string) => {
|
|
|
- let response = null
|
|
|
+ try {
|
|
|
+ response = JSON.parse(chunk)
|
|
|
+ } catch {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
|
|
|
- try {
|
|
|
- response = JSON.parse(chunk)
|
|
|
- } catch {
|
|
|
- // ignore
|
|
|
- }
|
|
|
+ if (response?.detail?.type === 'invalid_request_error') {
|
|
|
+ const msg = `ChatGPT error ${response.detail.message}: ${response.detail.code} (${response.detail.type})`
|
|
|
+ const error = new types.ChatGPTError(msg, { cause: response })
|
|
|
+ error.statusCode = response.detail.code
|
|
|
+ error.statusText = response.detail.message
|
|
|
|
|
|
- if (response?.detail?.type === 'invalid_request_error') {
|
|
|
- const msg = `ChatGPT error ${response.detail.message}: ${response.detail.code} (${response.detail.type})`
|
|
|
- const error = new types.ChatGPTError(msg, { cause: response })
|
|
|
- error.statusCode = response.detail.code
|
|
|
- error.statusText = response.detail.message
|
|
|
+ if (onError) {
|
|
|
+ onError(error)
|
|
|
+ } else {
|
|
|
+ console.error(error)
|
|
|
+ }
|
|
|
|
|
|
- if (onError) {
|
|
|
- onError(error)
|
|
|
- } else {
|
|
|
- console.error(error)
|
|
|
- }
|
|
|
+ // don't feed to the event parser
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- // don't feed to the event parser
|
|
|
- return
|
|
|
+ parser.feed(chunk)
|
|
|
}
|
|
|
|
|
|
- parser.feed(chunk)
|
|
|
- }
|
|
|
+ if (!res.body.getReader) {
|
|
|
+ // Vercel polyfills `fetch` with `node-fetch`, which doesn't conform to
|
|
|
+ // web standards, so this is a workaround...
|
|
|
+ const body: NodeJS.ReadableStream = res.body as any
|
|
|
|
|
|
- if (!res.body.getReader) {
|
|
|
- // Vercel polyfills `fetch` with `node-fetch`, which doesn't conform to
|
|
|
- // web standards, so this is a workaround...
|
|
|
- const body: NodeJS.ReadableStream = res.body as any
|
|
|
+ if (!body.on || !body.read) {
|
|
|
+ throw new types.ChatGPTError('unsupported "fetch" implementation')
|
|
|
+ }
|
|
|
|
|
|
- if (!body.on || !body.read) {
|
|
|
- throw new types.ChatGPTError('unsupported "fetch" implementation')
|
|
|
- }
|
|
|
-
|
|
|
- body.on('readable', () => {
|
|
|
- let chunk: string | Buffer
|
|
|
- while (null !== (chunk = body.read())) {
|
|
|
- feed(chunk.toString())
|
|
|
- }
|
|
|
- })
|
|
|
- } else {
|
|
|
- for await (const chunk of streamAsyncIterable(res.body)) {
|
|
|
- const str = new TextDecoder().decode(chunk)
|
|
|
- feed(str)
|
|
|
+ body.on('readable', () => {
|
|
|
+ let chunk: string | Buffer
|
|
|
+ while (null !== (chunk = body.read())) {
|
|
|
+ feed(chunk.toString())
|
|
|
+ }
|
|
|
+ })
|
|
|
+ body.on('end', () => {
|
|
|
+ onMessage('[DONE]')
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ for await (const chunk of streamAsyncIterable(res.body)) {
|
|
|
+ const str = new TextDecoder().decode(chunk)
|
|
|
+ feed(str)
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
}
|