Skip to content

Commit eddb247

Browse files
authored
Merge pull request #335 from openai/release-please--branches--master--changes--next--components--openai
2 parents 18db571 + 3e28dcf commit eddb247

File tree

10 files changed

+140
-102
lines changed

10 files changed

+140
-102
lines changed

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "4.10.0"
2+
".": "4.11.0"
33
}

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
# Changelog
22

3+
## 4.11.0 (2023-09-29)
4+
5+
Full Changelog: [v4.10.0...v4.11.0](https://github.com/openai/openai-node/compare/v4.10.0...v4.11.0)
6+
7+
### Features
8+
9+
* **client:** handle retry-after with a date ([#340](https://github.com/openai/openai-node/issues/340)) ([b6dd384](https://github.com/openai/openai-node/commit/b6dd38488ea7cc4c22495f16d027b7ffdb87da53))
10+
* **package:** export a root error type ([#338](https://github.com/openai/openai-node/issues/338)) ([462bcda](https://github.com/openai/openai-node/commit/462bcda7140611afa20bc25de4aec6d4b205b37d))
11+
12+
13+
### Bug Fixes
14+
15+
* **api:** add content_filter to chat completion finish reason ([#344](https://github.com/openai/openai-node/issues/344)) ([f10c757](https://github.com/openai/openai-node/commit/f10c757d831d90407ba47b4659d9cd34b1a35b1d))
16+
17+
18+
### Chores
19+
20+
* **internal:** bump lock file ([#334](https://github.com/openai/openai-node/issues/334)) ([fd2337b](https://github.com/openai/openai-node/commit/fd2337b018ab2f31bcea8f9feda0ddaf755390c7))
21+
* **internal:** update lock file ([#339](https://github.com/openai/openai-node/issues/339)) ([1bf84b6](https://github.com/openai/openai-node/commit/1bf84b672c386f8ca46bb8fc120eb8d8d48b3a82))
22+
* **internal:** update lock file ([#342](https://github.com/openai/openai-node/issues/342)) ([0001f06](https://github.com/openai/openai-node/commit/0001f062728b0e2047d2bf03b9d947a4be0c7206))
23+
* **internal:** update lock file ([#343](https://github.com/openai/openai-node/issues/343)) ([a02ac8e](https://github.com/openai/openai-node/commit/a02ac8e7f881551527a3cbcadad53b7e424650e8))
24+
325
## 4.10.0 (2023-09-21)
426

527
Full Changelog: [v4.9.1...v4.10.0](https://github.com/openai/openai-node/compare/v4.9.1...v4.10.0)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "openai",
3-
"version": "4.10.0",
3+
"version": "4.11.0",
44
"description": "Client library for the OpenAI API",
55
"author": "OpenAI <[email protected]>",
66
"types": "dist/index.d.ts",

src/core.ts

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { VERSION } from './version';
22
import { Stream } from './streaming';
3-
import { APIError, APIConnectionError, APIConnectionTimeoutError, APIUserAbortError } from './error';
3+
import {
4+
OpenAIError,
5+
APIError,
6+
APIConnectionError,
7+
APIConnectionTimeoutError,
8+
APIUserAbortError,
9+
} from './error';
410
import {
511
kind as shimsKind,
612
type Readable,
@@ -440,7 +446,7 @@ export abstract class APIClient {
440446
if (value === null) {
441447
return `${encodeURIComponent(key)}=`;
442448
}
443-
throw new Error(
449+
throw new OpenAIError(
444450
`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.`,
445451
);
446452
})
@@ -503,32 +509,37 @@ export abstract class APIClient {
503509
retriesRemaining -= 1;
504510

505511
// About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
506-
//
507-
// TODO: we may want to handle the case where the header is using the http-date syntax: "Retry-After: <http-date>".
508-
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax for details.
509-
const retryAfter = parseInt(responseHeaders?.['retry-after'] || '');
512+
let timeoutMillis: number | undefined;
513+
const retryAfterHeader = responseHeaders?.['retry-after'];
514+
if (retryAfterHeader) {
515+
const timeoutSeconds = parseInt(retryAfterHeader);
516+
if (!Number.isNaN(timeoutSeconds)) {
517+
timeoutMillis = timeoutSeconds * 1000;
518+
} else {
519+
timeoutMillis = Date.parse(retryAfterHeader) - Date.now();
520+
}
521+
}
510522

511-
const maxRetries = options.maxRetries ?? this.maxRetries;
512-
const timeout = this.calculateRetryTimeoutSeconds(retriesRemaining, retryAfter, maxRetries) * 1000;
513-
await sleep(timeout);
523+
// If the API asks us to wait a certain amount of time (and it's a reasonable amount),
524+
// just do what it says, but otherwise calculate a default
525+
if (
526+
!timeoutMillis ||
527+
!Number.isInteger(timeoutMillis) ||
528+
timeoutMillis <= 0 ||
529+
timeoutMillis > 60 * 1000
530+
) {
531+
const maxRetries = options.maxRetries ?? this.maxRetries;
532+
timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries);
533+
}
534+
await sleep(timeoutMillis);
514535

515536
return this.makeRequest(options, retriesRemaining);
516537
}
517538

518-
private calculateRetryTimeoutSeconds(
519-
retriesRemaining: number,
520-
retryAfter: number,
521-
maxRetries: number,
522-
): number {
539+
private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number {
523540
const initialRetryDelay = 0.5;
524541
const maxRetryDelay = 2;
525542

526-
// If the API asks us to wait a certain amount of time (and it's a reasonable amount),
527-
// just do what it says.
528-
if (Number.isInteger(retryAfter) && retryAfter <= 60) {
529-
return retryAfter;
530-
}
531-
532543
const numRetries = maxRetries - retriesRemaining;
533544

534545
// Apply exponential backoff, but not more than the max.
@@ -537,7 +548,7 @@ export abstract class APIClient {
537548
// Apply some jitter, plus-or-minus half a second.
538549
const jitter = Math.random() - 0.5;
539550

540-
return sleepSeconds + jitter;
551+
return (sleepSeconds + jitter) * 1000;
541552
}
542553

543554
private getUserAgent(): string {
@@ -599,7 +610,7 @@ export abstract class AbstractPage<Item> implements AsyncIterable<Item> {
599610
async getNextPage(): Promise<this> {
600611
const nextInfo = this.nextPageInfo();
601612
if (!nextInfo) {
602-
throw new Error(
613+
throw new OpenAIError(
603614
'No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.',
604615
);
605616
}
@@ -925,10 +936,10 @@ export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve
925936

926937
const validatePositiveInteger = (name: string, n: unknown): number => {
927938
if (typeof n !== 'number' || !Number.isInteger(n)) {
928-
throw new Error(`${name} must be an integer`);
939+
throw new OpenAIError(`${name} must be an integer`);
929940
}
930941
if (n < 0) {
931-
throw new Error(`${name} must be a positive integer`);
942+
throw new OpenAIError(`${name} must be a positive integer`);
932943
}
933944
return n;
934945
};
@@ -939,7 +950,7 @@ export const castToError = (err: any): Error => {
939950
};
940951

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

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

965-
throw new Error(`Could not coerce ${value} (type: ${typeof value}) into a number`);
976+
throw new OpenAIError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
966977
};
967978

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

972-
throw new Error(`Could not coerce ${value} (type: ${typeof value}) into a number`);
983+
throw new OpenAIError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
973984
};
974985

975986
export const coerceBoolean = (value: unknown): boolean => {
@@ -1073,5 +1084,5 @@ export const toBase64 = (str: string | null | undefined): string => {
10731084
return btoa(str);
10741085
}
10751086

1076-
throw new Error('Cannot generate b64 string; Expected `Buffer` or `btoa` to be defined');
1087+
throw new OpenAIError('Cannot generate b64 string; Expected `Buffer` or `btoa` to be defined');
10771088
};

src/error.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

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

5-
export class APIError extends Error {
5+
export class OpenAIError extends Error {}
6+
7+
export class APIError extends OpenAIError {
68
readonly status: number | undefined;
79
readonly headers: Headers | undefined;
810
readonly error: Object | undefined;

src/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export class OpenAI extends Core.APIClient {
103103
...opts
104104
}: ClientOptions = {}) {
105105
if (apiKey === undefined) {
106-
throw new Error(
106+
throw new Errors.OpenAIError(
107107
"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' }).",
108108
);
109109
}
@@ -116,7 +116,7 @@ export class OpenAI extends Core.APIClient {
116116
};
117117

118118
if (!options.dangerouslyAllowBrowser && Core.isRunningInBrowser()) {
119-
throw new Error(
119+
throw new Errors.OpenAIError(
120120
"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",
121121
);
122122
}
@@ -164,6 +164,7 @@ export class OpenAI extends Core.APIClient {
164164

165165
static OpenAI = this;
166166

167+
static OpenAIError = Errors.OpenAIError;
167168
static APIError = Errors.APIError;
168169
static APIConnectionError = Errors.APIConnectionError;
169170
static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError;
@@ -179,6 +180,7 @@ export class OpenAI extends Core.APIClient {
179180
}
180181

181182
export const {
183+
OpenAIError,
182184
APIError,
183185
APIConnectionError,
184186
APIConnectionTimeoutError,

src/resources/chat/completions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export namespace ChatCompletionChunk {
139139
* content was omitted due to a flag from our content filters, or `function_call`
140140
* if the model called a function.
141141
*/
142-
finish_reason: 'stop' | 'length' | 'function_call' | null;
142+
finish_reason: 'stop' | 'length' | 'function_call' | 'content_filter' | null;
143143

144144
/**
145145
* The index of the choice in the list of choices.

src/streaming.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type Response } from './_shims/index';
2+
import { OpenAIError } from './error';
23

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

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

@@ -198,7 +199,7 @@ class LineDecoder {
198199
return Buffer.from(bytes).toString();
199200
}
200201

201-
throw new Error(
202+
throw new OpenAIError(
202203
`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.`,
203204
);
204205
}
@@ -210,14 +211,14 @@ class LineDecoder {
210211
return this.textDecoder.decode(bytes);
211212
}
212213

213-
throw new Error(
214+
throw new OpenAIError(
214215
`Unexpected: received non-Uint8Array/ArrayBuffer (${
215216
(bytes as any).constructor.name
216217
}) in a web platform. Please report this error.`,
217218
);
218219
}
219220

220-
throw new Error(
221+
throw new OpenAIError(
221222
`Unexpected: neither Buffer nor TextDecoder are available as globals. Please report this error.`,
222223
);
223224
}

src/version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const VERSION = '4.10.0'; // x-release-please-version
1+
export const VERSION = '4.11.0'; // x-release-please-version

0 commit comments

Comments
 (0)