From 90c6e3500f1608570652a7168919374565f52d20 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Sat, 10 Dec 2022 16:19:35 -0600 Subject: [PATCH] feat: add ChatGPTError with more info on HTTP errors --- src/chatgpt-api.test.ts | 3 +++ src/chatgpt-api.ts | 46 ++++++++++++++++++++++++++++++++--------- src/fetch-sse.ts | 10 +++++++-- src/types.ts | 7 +++++++ 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/chatgpt-api.test.ts b/src/chatgpt-api.test.ts index fd4bdd6..78a06e4 100644 --- a/src/chatgpt-api.test.ts +++ b/src/chatgpt-api.test.ts @@ -1,6 +1,7 @@ import test from 'ava' import dotenv from 'dotenv-safe' +import * as types from './types' import { ChatGPTAPI } from './chatgpt-api' dotenv.config() @@ -20,6 +21,7 @@ test('ChatGPTAPI invalid session token', async (t) => { await chatgpt.ensureAuth() }, { + instanceOf: types.ChatGPTError, message: 'ChatGPT failed to refresh auth token. Error: Unauthorized' } ) @@ -64,6 +66,7 @@ if (!isCI) { await chatgpt.ensureAuth() }, { + instanceOf: types.ChatGPTError, message: '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 39d59f2..05551bb 100644 --- a/src/chatgpt-api.ts +++ b/src/chatgpt-api.ts @@ -1,5 +1,5 @@ import ExpiryMap from 'expiry-map' -import pTimeout, { TimeoutError } from 'p-timeout' +import pTimeout from 'p-timeout' import { v4 as uuidv4 } from 'uuid' import * as types from './types' @@ -68,7 +68,7 @@ export class ChatGPTAPI { this._accessTokenCache = new ExpiryMap(accessTokenTTL) if (!this._sessionToken) { - throw new Error('ChatGPT invalid session token') + throw new types.ChatGPTError('ChatGPT invalid session token') } } @@ -238,6 +238,7 @@ export class ChatGPTAPI { return cachedAccessToken } + let response: Response try { const res = await fetch('https://chat.openai.com/api/auth/session', { headers: { @@ -245,8 +246,14 @@ export class ChatGPTAPI { 'user-agent': this._userAgent } }).then((r) => { + response = r + if (!r.ok) { - throw new Error(`${r.status} ${r.statusText}`) + const error = new types.ChatGPTError(`${r.status} ${r.statusText}`) + error.response = r + error.statusCode = r.status + error.statusText = r.statusText + throw error } return r.json() as any as types.SessionResult @@ -255,22 +262,41 @@ export class ChatGPTAPI { const accessToken = res?.accessToken if (!accessToken) { - throw new Error('Unauthorized') + const error = new types.ChatGPTError('Unauthorized') + error.response = response + error.statusCode = response?.status + error.statusText = response?.statusText + throw error } - const error = res?.error - if (error) { - if (error === 'RefreshAccessTokenError') { - throw new Error('session token may have expired') + const appError = res?.error + if (appError) { + if (appError === 'RefreshAccessTokenError') { + const error = new types.ChatGPTError('session token may have expired') + error.response = response + error.statusCode = response?.status + error.statusText = response?.statusText + throw error } else { - throw new Error(error) + const error = new types.ChatGPTError(appError) + error.response = response + error.statusCode = response?.status + error.statusText = response?.statusText + throw error } } this._accessTokenCache.set(KEY_ACCESS_TOKEN, accessToken) return accessToken } catch (err: any) { - throw new Error(`ChatGPT failed to refresh auth token. ${err.toString()}`) + const error = new types.ChatGPTError( + `ChatGPT failed to refresh auth token. ${err.toString()}` + ) + error.response = response + error.statusCode = response?.status + error.statusText = response?.statusText + error.originalError = err + throw error } } diff --git a/src/fetch-sse.ts b/src/fetch-sse.ts index 40f2c6f..3b8fb50 100644 --- a/src/fetch-sse.ts +++ b/src/fetch-sse.ts @@ -1,5 +1,6 @@ import { createParser } from 'eventsource-parser' +import * as types from './types' import { fetch } from './fetch' import { streamAsyncIterable } from './stream-async-iterable' @@ -10,7 +11,12 @@ export async function fetchSSE( const { onMessage, ...fetchOptions } = options const res = await fetch(url, fetchOptions) if (!res.ok) { - throw new Error(`ChatGPTAPI error ${res.status || res.statusText}`) + const msg = `ChatGPTAPI error ${res.status || res.statusText}` + const error = new types.ChatGPTError(msg) + error.statusCode = res.status + error.statusText = res.statusText + error.response = res + throw error } const parser = createParser((event) => { @@ -25,7 +31,7 @@ export async function fetchSSE( const body: NodeJS.ReadableStream = res.body as any if (!body.on || !body.read) { - throw new Error('unsupported "fetch" implementation') + throw new types.ChatGPTError('unsupported "fetch" implementation') } body.on('readable', () => { diff --git a/src/types.ts b/src/types.ts index ae73f6c..0e69c4b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -287,3 +287,10 @@ export type SendConversationMessageOptions = Omit< SendMessageOptions, 'conversationId' | 'parentMessageId' > + +export class ChatGPTError extends Error { + statusCode?: number + statusText?: string + response?: Response + originalError?: Error +}