Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(package): export a root error type #338

Merged
merged 1 commit into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions src/core.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { VERSION } from './version';
import { Stream } from './streaming';
import { APIError, APIConnectionError, APIConnectionTimeoutError, APIUserAbortError } from './error';
import {
OpenAIError,
APIError,
APIConnectionError,
APIConnectionTimeoutError,
APIUserAbortError,
} from './error';
import {
kind as shimsKind,
type Readable,
Expand Down Expand Up @@ -440,7 +446,7 @@ export abstract class APIClient {
if (value === null) {
return `${encodeURIComponent(key)}=`;
}
throw new Error(
throw new OpenAIError(
`Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`,
);
})
Expand Down Expand Up @@ -599,7 +605,7 @@ export abstract class AbstractPage<Item> implements AsyncIterable<Item> {
async getNextPage(): Promise<this> {
const nextInfo = this.nextPageInfo();
if (!nextInfo) {
throw new Error(
throw new OpenAIError(
'No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.',
);
}
Expand Down Expand Up @@ -925,10 +931,10 @@ export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve

const validatePositiveInteger = (name: string, n: unknown): number => {
if (typeof n !== 'number' || !Number.isInteger(n)) {
throw new Error(`${name} must be an integer`);
throw new OpenAIError(`${name} must be an integer`);
}
if (n < 0) {
throw new Error(`${name} must be a positive integer`);
throw new OpenAIError(`${name} must be a positive integer`);
}
return n;
};
Expand All @@ -939,7 +945,7 @@ export const castToError = (err: any): Error => {
};

export const ensurePresent = <T>(value: T | null | undefined): T => {
if (value == null) throw new Error(`Expected a value to be given but received ${value} instead.`);
if (value == null) throw new OpenAIError(`Expected a value to be given but received ${value} instead.`);
return value;
};

Expand All @@ -962,14 +968,14 @@ export const coerceInteger = (value: unknown): number => {
if (typeof value === 'number') return Math.round(value);
if (typeof value === 'string') return parseInt(value, 10);

throw new Error(`Could not coerce ${value} (type: ${typeof value}) into a number`);
throw new OpenAIError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};

export const coerceFloat = (value: unknown): number => {
if (typeof value === 'number') return value;
if (typeof value === 'string') return parseFloat(value);

throw new Error(`Could not coerce ${value} (type: ${typeof value}) into a number`);
throw new OpenAIError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};

export const coerceBoolean = (value: unknown): boolean => {
Expand Down Expand Up @@ -1073,5 +1079,5 @@ export const toBase64 = (str: string | null | undefined): string => {
return btoa(str);
}

throw new Error('Cannot generate b64 string; Expected `Buffer` or `btoa` to be defined');
throw new OpenAIError('Cannot generate b64 string; Expected `Buffer` or `btoa` to be defined');
};
4 changes: 3 additions & 1 deletion src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import { castToError, Headers } from './core';

export class APIError extends Error {
export class OpenAIError extends Error {}

export class APIError extends OpenAIError {
readonly status: number | undefined;
readonly headers: Headers | undefined;
readonly error: Object | undefined;
Expand Down
6 changes: 4 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export class OpenAI extends Core.APIClient {
...opts
}: ClientOptions = {}) {
if (apiKey === undefined) {
throw new Error(
throw new Errors.OpenAIError(
"The OPENAI_API_KEY environment variable is missing or empty; either provide it, or instantiate the OpenAI client with an apiKey option, like new OpenAI({ apiKey: 'my apiKey' }).",
);
}
Expand All @@ -116,7 +116,7 @@ export class OpenAI extends Core.APIClient {
};

if (!options.dangerouslyAllowBrowser && Core.isRunningInBrowser()) {
throw new Error(
throw new Errors.OpenAIError(
"It looks like you're running in a browser-like environment.\n\nThis is disabled by default, as it risks exposing your secret API credentials to attackers.\nIf you understand the risks and have appropriate mitigations in place,\nyou can set the `dangerouslyAllowBrowser` option to `true`, e.g.,\n\nnew OpenAI({ apiKey, dangerouslyAllowBrowser: true });\n\nhttps://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety\n",
);
}
Expand Down Expand Up @@ -164,6 +164,7 @@ export class OpenAI extends Core.APIClient {

static OpenAI = this;

static OpenAIError = Errors.OpenAIError;
static APIError = Errors.APIError;
static APIConnectionError = Errors.APIConnectionError;
static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError;
Expand All @@ -179,6 +180,7 @@ export class OpenAI extends Core.APIClient {
}

export const {
OpenAIError,
APIError,
APIConnectionError,
APIConnectionTimeoutError,
Expand Down
9 changes: 5 additions & 4 deletions src/streaming.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type Response } from './_shims/index';
import { OpenAIError } from './error';

type Bytes = string | ArrayBuffer | Uint8Array | Buffer | null | undefined;

Expand All @@ -23,7 +24,7 @@ export class Stream<Item> implements AsyncIterable<Item> {
private async *iterMessages(): AsyncGenerator<ServerSentEvent, void, unknown> {
if (!this.response.body) {
this.controller.abort();
throw new Error(`Attempted to iterate over a response with no body`);
throw new OpenAIError(`Attempted to iterate over a response with no body`);
}
const lineDecoder = new LineDecoder();

Expand Down Expand Up @@ -198,7 +199,7 @@ class LineDecoder {
return Buffer.from(bytes).toString();
}

throw new Error(
throw new OpenAIError(
`Unexpected: received non-Uint8Array (${bytes.constructor.name}) stream chunk in an environment with a global "Buffer" defined, which this library assumes to be Node. Please report this error.`,
);
}
Expand All @@ -210,14 +211,14 @@ class LineDecoder {
return this.textDecoder.decode(bytes);
}

throw new Error(
throw new OpenAIError(
`Unexpected: received non-Uint8Array/ArrayBuffer (${
(bytes as any).constructor.name
}) in a web platform. Please report this error.`,
);
}

throw new Error(
throw new OpenAIError(
`Unexpected: neither Buffer nor TextDecoder are available as globals. Please report this error.`,
);
}
Expand Down