From b176703102998f0e9d8ca2ed93ccd495fd10a6ee Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Wed, 11 Oct 2023 21:57:18 +0100 Subject: [PATCH 01/12] chore: show deprecation notice on re-export (#368) --- README.md | 2 +- src/resources/chat/chat.ts | 3 +++ src/resources/chat/completions.ts | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 30220f95b..856f3e171 100644 --- a/README.md +++ b/README.md @@ -297,7 +297,7 @@ await openai.models.list({ ## Semantic Versioning -This package generally attempts to follow [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: +This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: 1. Changes that only affect static types, without breaking runtime behavior. 2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals)_. diff --git a/src/resources/chat/chat.ts b/src/resources/chat/chat.ts index ac510d7bd..35011c7a5 100644 --- a/src/resources/chat/chat.ts +++ b/src/resources/chat/chat.ts @@ -14,6 +14,9 @@ export namespace Chat { export import ChatCompletionMessage = CompletionsAPI.ChatCompletionMessage; export import ChatCompletionMessageParam = CompletionsAPI.ChatCompletionMessageParam; export import ChatCompletionRole = CompletionsAPI.ChatCompletionRole; + /** + * @deprecated ChatCompletionMessageParam should be used instead + */ export import CreateChatCompletionRequestMessage = CompletionsAPI.CreateChatCompletionRequestMessage; export import ChatCompletionCreateParams = CompletionsAPI.ChatCompletionCreateParams; export import CompletionCreateParams = CompletionsAPI.CompletionCreateParams; diff --git a/src/resources/chat/completions.ts b/src/resources/chat/completions.ts index d513b0f20..a5be20771 100644 --- a/src/resources/chat/completions.ts +++ b/src/resources/chat/completions.ts @@ -509,6 +509,9 @@ export namespace Completions { export import ChatCompletionMessage = ChatCompletionsAPI.ChatCompletionMessage; export import ChatCompletionMessageParam = ChatCompletionsAPI.ChatCompletionMessageParam; export import ChatCompletionRole = ChatCompletionsAPI.ChatCompletionRole; + /** + * @deprecated ChatCompletionMessageParam should be used instead + */ export import CreateChatCompletionRequestMessage = ChatCompletionsAPI.CreateChatCompletionRequestMessage; export import ChatCompletionCreateParams = ChatCompletionsAPI.ChatCompletionCreateParams; export import CompletionCreateParams = ChatCompletionsAPI.CompletionCreateParams; From 71984edc3141ba99ffa1327bab6a182b4452209f Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Thu, 12 Oct 2023 01:23:23 +0100 Subject: [PATCH 02/12] refactor(streaming): change Stream constructor signature (#370) --- package.json | 3 +- src/_shims/auto/types.d.ts | 4 +- src/_shims/index.d.ts | 2 + src/_shims/node-runtime.ts | 2 + src/_shims/node-types.d.ts | 2 +- src/_shims/registry.ts | 3 + src/_shims/web-runtime.ts | 12 +++ src/_shims/web-types.d.ts | 3 +- src/core.ts | 2 +- src/error.ts | 9 +- src/shims/node.ts | 2 +- src/shims/web.ts | 2 +- src/streaming.ts | 196 ++++++++++++++++++++++++++++--------- yarn.lock | 5 + 14 files changed, 192 insertions(+), 55 deletions(-) diff --git a/package.json b/package.json index e4ee62738..2e728d452 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,8 @@ "digest-fetch": "^1.3.0", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" + "node-fetch": "^2.6.7", + "web-streams-polyfill": "^3.2.1" }, "devDependencies": { "@types/jest": "^29.4.0", diff --git a/src/_shims/auto/types.d.ts b/src/_shims/auto/types.d.ts index 9c1cc2550..d7755070b 100644 --- a/src/_shims/auto/types.d.ts +++ b/src/_shims/auto/types.d.ts @@ -96,4 +96,6 @@ export declare class FsReadStream extends Readable { // @ts-ignore type _ReadableStream = unknown extends ReadableStream ? never : ReadableStream; -export { type _ReadableStream as ReadableStream }; +// @ts-ignore +declare const _ReadableStream: unknown extends typeof ReadableStream ? never : typeof ReadableStream; +export { _ReadableStream as ReadableStream }; diff --git a/src/_shims/index.d.ts b/src/_shims/index.d.ts index 044f2cfcf..4e52b952e 100644 --- a/src/_shims/index.d.ts +++ b/src/_shims/index.d.ts @@ -62,6 +62,8 @@ export type Readable = SelectType; export type FsReadStream = SelectType; // @ts-ignore export type ReadableStream = SelectType; +// @ts-ignore +export const ReadableStream: SelectType; export function getMultipartRequestOptions>( form: FormData, diff --git a/src/_shims/node-runtime.ts b/src/_shims/node-runtime.ts index e2398e2b3..cc16e8542 100644 --- a/src/_shims/node-runtime.ts +++ b/src/_shims/node-runtime.ts @@ -13,6 +13,7 @@ import { Readable } from 'node:stream'; import { type RequestOptions } from '../core'; import { MultipartBody } from './MultipartBody'; import { type Shims } from './registry'; +import { ReadableStream } from 'web-streams-polyfill'; type FileFromPathOptions = Omit; @@ -71,6 +72,7 @@ export function getRuntime(): Shims { FormData: fd.FormData, Blob: fd.Blob, File: fd.File, + ReadableStream, getMultipartRequestOptions, getDefaultAgent: (url: string): Agent => (url.startsWith('https') ? defaultHttpsAgent : defaultHttpAgent), fileFromPath, diff --git a/src/_shims/node-types.d.ts b/src/_shims/node-types.d.ts index 28fe60499..b31698f78 100644 --- a/src/_shims/node-types.d.ts +++ b/src/_shims/node-types.d.ts @@ -7,7 +7,7 @@ import * as fd from 'formdata-node'; export { type Agent } from 'node:http'; export { type Readable } from 'node:stream'; export { type ReadStream as FsReadStream } from 'node:fs'; -export { type ReadableStream } from 'web-streams-polyfill'; +export { ReadableStream } from 'web-streams-polyfill'; export const fetch: typeof nf.default; diff --git a/src/_shims/registry.ts b/src/_shims/registry.ts index 0e0706877..65b570d0c 100644 --- a/src/_shims/registry.ts +++ b/src/_shims/registry.ts @@ -12,6 +12,7 @@ export interface Shims { FormData: any; Blob: any; File: any; + ReadableStream: any; getMultipartRequestOptions: >( form: Shims['FormData'], opts: RequestOptions, @@ -32,6 +33,7 @@ export let Headers: Shims['Headers'] | undefined = undefined; export let FormData: Shims['FormData'] | undefined = undefined; export let Blob: Shims['Blob'] | undefined = undefined; export let File: Shims['File'] | undefined = undefined; +export let ReadableStream: Shims['ReadableStream'] | undefined = undefined; export let getMultipartRequestOptions: Shims['getMultipartRequestOptions'] | undefined = undefined; export let getDefaultAgent: Shims['getDefaultAgent'] | undefined = undefined; export let fileFromPath: Shims['fileFromPath'] | undefined = undefined; @@ -55,6 +57,7 @@ export function setShims(shims: Shims, options: { auto: boolean } = { auto: fals FormData = shims.FormData; Blob = shims.Blob; File = shims.File; + ReadableStream = shims.ReadableStream; getMultipartRequestOptions = shims.getMultipartRequestOptions; getDefaultAgent = shims.getDefaultAgent; fileFromPath = shims.fileFromPath; diff --git a/src/_shims/web-runtime.ts b/src/_shims/web-runtime.ts index 12d73e965..92dadeb89 100644 --- a/src/_shims/web-runtime.ts +++ b/src/_shims/web-runtime.ts @@ -72,6 +72,18 @@ export function getRuntime({ manuallyImported }: { manuallyImported?: boolean } } } ), + ReadableStream: + // @ts-ignore + typeof ReadableStream !== 'undefined' ? ReadableStream : ( + class ReadableStream { + // @ts-ignore + constructor() { + throw new Error( + `streaming isn't supported in this environment yet as 'ReadableStream' is undefined. ${recommendation}`, + ); + } + } + ), getMultipartRequestOptions: async >( // @ts-ignore form: FormData, diff --git a/src/_shims/web-types.d.ts b/src/_shims/web-types.d.ts index ec96cd817..4ff351383 100644 --- a/src/_shims/web-types.d.ts +++ b/src/_shims/web-types.d.ts @@ -79,4 +79,5 @@ export declare class FsReadStream extends Readable { } type _ReadableStream = ReadableStream; -export { type _ReadableStream as ReadableStream }; +declare const _ReadableStream: typeof ReadableStream; +export { _ReadableStream as ReadableStream }; diff --git a/src/core.ts b/src/core.ts index a715f4507..73b2f5574 100644 --- a/src/core.ts +++ b/src/core.ts @@ -44,7 +44,7 @@ async function defaultParseResponse(props: APIResponseProps): Promise { if (props.options.stream) { // Note: there is an invariant here that isn't represented in the type system // that if you set `stream: true` the response type must also be `Stream` - return new Stream(response, props.controller) as any; + return Stream.fromSSEResponse(response, props.controller) as any; } const contentType = response.headers.get('content-type'); diff --git a/src/error.ts b/src/error.ts index 873087c78..28a8b540f 100644 --- a/src/error.ts +++ b/src/error.ts @@ -19,7 +19,7 @@ export class APIError extends OpenAIError { message: string | undefined, headers: Headers | undefined, ) { - super(`${status} ${APIError.makeMessage(error, message)}`); + super(`${APIError.makeMessage(status, error, message)}`); this.status = status; this.headers = headers; @@ -30,13 +30,14 @@ export class APIError extends OpenAIError { this.type = data?.['type']; } - private static makeMessage(error: any, message: string | undefined) { + private static makeMessage(status: number | undefined, error: any, message: string | undefined) { return ( - error?.message ? + (status || '') + + (error?.message ? typeof error.message === 'string' ? error.message : JSON.stringify(error.message) : error ? JSON.stringify(error) - : message || 'status code (no body)' + : message || 'status code (no body)') ); } diff --git a/src/shims/node.ts b/src/shims/node.ts index 9273d4eae..73df5600c 100644 --- a/src/shims/node.ts +++ b/src/shims/node.ts @@ -45,6 +45,6 @@ declare module '../_shims/manual-types' { // @ts-ignore export type FsReadStream = types.FsReadStream; // @ts-ignore - export type ReadableStream = types.ReadableStream; + export import ReadableStream = types.ReadableStream; } } diff --git a/src/shims/web.ts b/src/shims/web.ts index 9970f1db8..f72d78444 100644 --- a/src/shims/web.ts +++ b/src/shims/web.ts @@ -45,6 +45,6 @@ declare module '../_shims/manual-types' { // @ts-ignore export type FsReadStream = types.FsReadStream; // @ts-ignore - export type ReadableStream = types.ReadableStream; + export import ReadableStream = types.ReadableStream; } } diff --git a/src/streaming.ts b/src/streaming.ts index 7f9ebe0a0..f69724d64 100644 --- a/src/streaming.ts +++ b/src/streaming.ts @@ -1,4 +1,4 @@ -import { type Response } from './_shims/index'; +import { ReadableStream, type Response } from './_shims/index'; import { OpenAIError } from './error'; type Bytes = string | ArrayBuffer | Uint8Array | Buffer | null | undefined; @@ -12,67 +12,175 @@ type ServerSentEvent = { export class Stream implements AsyncIterable { controller: AbortController; - private response: Response; - private decoder: SSEDecoder; - - constructor(response: Response, controller: AbortController) { - this.response = response; + constructor(private iterator: () => AsyncIterator, controller: AbortController) { this.controller = controller; - this.decoder = new SSEDecoder(); } - private async *iterMessages(): AsyncGenerator { - if (!this.response.body) { - this.controller.abort(); - throw new OpenAIError(`Attempted to iterate over a response with no body`); - } + static fromSSEResponse(response: Response, controller: AbortController) { + let consumed = false; + const decoder = new SSEDecoder(); + + async function* iterMessages(): AsyncGenerator { + if (!response.body) { + controller.abort(); + throw new OpenAIError(`Attempted to iterate over a response with no body`); + } + + const lineDecoder = new LineDecoder(); - const lineDecoder = new LineDecoder(); + const iter = readableStreamAsyncIterable(response.body); + for await (const chunk of iter) { + for (const line of lineDecoder.decode(chunk)) { + const sse = decoder.decode(line); + if (sse) yield sse; + } + } - const iter = readableStreamAsyncIterable(this.response.body); - for await (const chunk of iter) { - for (const line of lineDecoder.decode(chunk)) { - const sse = this.decoder.decode(line); + for (const line of lineDecoder.flush()) { + const sse = decoder.decode(line); if (sse) yield sse; } } - for (const line of lineDecoder.flush()) { - const sse = this.decoder.decode(line); - if (sse) yield sse; + async function* iterator(): AsyncIterator { + if (consumed) { + throw new Error('Cannot iterate over a consumed stream, use `.tee()` to split the stream.'); + } + consumed = true; + let done = false; + try { + for await (const sse of iterMessages()) { + if (done) continue; + + if (sse.data.startsWith('[DONE]')) { + done = true; + continue; + } + + if (sse.event === null) { + try { + yield JSON.parse(sse.data); + } catch (e) { + console.error(`Could not parse message into JSON:`, sse.data); + console.error(`From chunk:`, sse.raw); + throw e; + } + } + } + done = true; + } catch (e) { + // If the user calls `stream.controller.abort()`, we should exit without throwing. + if (e instanceof Error && e.name === 'AbortError') return; + throw e; + } finally { + // If the user `break`s, abort the ongoing request. + if (!done) controller.abort(); + } } + + return new Stream(iterator, controller); } - async *[Symbol.asyncIterator](): AsyncIterator { - let done = false; - try { - for await (const sse of this.iterMessages()) { - if (done) continue; + // Generates a Stream from a newline-separated ReadableStream where each item + // is a JSON Value. + static fromReadableStream(readableStream: ReadableStream, controller: AbortController) { + let consumed = false; - if (sse.data.startsWith('[DONE]')) { - done = true; - continue; + async function* iterLines(): AsyncGenerator { + const lineDecoder = new LineDecoder(); + + const iter = readableStreamAsyncIterable(readableStream); + for await (const chunk of iter) { + for (const line of lineDecoder.decode(chunk)) { + yield line; } + } - if (sse.event === null) { - try { - yield JSON.parse(sse.data); - } catch (e) { - console.error(`Could not parse message into JSON:`, sse.data); - console.error(`From chunk:`, sse.raw); - throw e; - } + for (const line of lineDecoder.flush()) { + yield line; + } + } + + async function* iterator(): AsyncIterator { + if (consumed) { + throw new Error('Cannot iterate over a consumed stream, use `.tee()` to split the stream.'); + } + consumed = true; + let done = false; + try { + for await (const line of iterLines()) { + if (done) continue; + if (line) yield JSON.parse(line); } + done = true; + } catch (e) { + // If the user calls `stream.controller.abort()`, we should exit without throwing. + if (e instanceof Error && e.name === 'AbortError') return; + throw e; + } finally { + // If the user `break`s, abort the ongoing request. + if (!done) controller.abort(); } - done = true; - } catch (e) { - // If the user calls `stream.controller.abort()`, we should exit without throwing. - if (e instanceof Error && e.name === 'AbortError') return; - throw e; - } finally { - // If the user `break`s, abort the ongoing request. - if (!done) this.controller.abort(); } + + return new Stream(iterator, controller); + } + + [Symbol.asyncIterator](): AsyncIterator { + return this.iterator(); + } + + tee(): [Stream, Stream] { + const left: Array>> = []; + const right: Array>> = []; + const iterator = this.iterator(); + + const teeIterator = (queue: Array>>): AsyncIterator => { + return { + next: () => { + if (queue.length === 0) { + const result = iterator.next(); + left.push(result); + right.push(result); + } + return queue.shift()!; + }, + }; + }; + + return [ + new Stream(() => teeIterator(left), this.controller), + new Stream(() => teeIterator(right), this.controller), + ]; + } + + // Converts this stream to a newline-separated ReadableStream of JSON Stringified values in the stream + // which can be turned back into a Stream with Stream.fromReadableStream. + toReadableStream(): ReadableStream { + const self = this; + let iter: AsyncIterator; + const encoder = new TextEncoder(); + + return new ReadableStream({ + async start() { + iter = self[Symbol.asyncIterator](); + }, + async pull(ctrl) { + try { + const { value, done } = await iter.next(); + if (done) return ctrl.close(); + + const bytes = encoder.encode(JSON.stringify(value) + '\n'); + + ctrl.enqueue(bytes); + } catch (err) { + ctrl.error(err); + } + }, + async cancel() { + await iter.return?.(); + }, + }); } } diff --git a/yarn.lock b/yarn.lock index fb2b9a2c1..d01ab81de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4067,6 +4067,11 @@ web-streams-polyfill@4.0.0-beta.1: resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.1.tgz#3b19b9817374b7cee06d374ba7eeb3aeb80e8c95" integrity sha512-3ux37gEX670UUphBF9AMCq8XM6iQ8Ac6A+DSRRjDoRBm1ufCkaCDdNVbaqq60PsEkdNlLKrGtv/YBP4EJXqNtQ== +web-streams-polyfill@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" + integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" From e0d459f958451a99e15a11a0e5ea6471abbe1ac1 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Thu, 12 Oct 2023 02:30:22 +0100 Subject: [PATCH 03/12] refactor(test): refactor authentication tests (#371) --- README.md | 4 +- src/index.ts | 17 +++--- .../audio/transcriptions.test.ts | 2 +- .../api-resources/audio/translations.test.ts | 2 +- tests/api-resources/chat/completions.test.ts | 2 +- tests/api-resources/completions.test.ts | 2 +- tests/api-resources/edits.test.ts | 2 +- tests/api-resources/embeddings.test.ts | 2 +- tests/api-resources/files.test.ts | 2 +- tests/api-resources/fine-tunes.test.ts | 2 +- tests/api-resources/fine-tuning/jobs.test.ts | 2 +- tests/api-resources/images.test.ts | 2 +- tests/api-resources/models.test.ts | 2 +- tests/api-resources/moderations.test.ts | 2 +- tests/index.test.ts | 57 +++++++------------ 15 files changed, 45 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 856f3e171..3ebda69ca 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The full API of this library can be found in [api.md file](https://github.com/op import OpenAI from 'openai'; const openai = new OpenAI({ - apiKey: 'my api key', // defaults to process.env["OPENAI_API_KEY"] + apiKey: 'My API Key', // defaults to process.env["OPENAI_API_KEY"] }); async function main() { @@ -73,7 +73,7 @@ This library includes TypeScript definitions for all request params and response import OpenAI from 'openai'; const openai = new OpenAI({ - apiKey: 'my api key', // defaults to process.env["OPENAI_API_KEY"] + apiKey: 'My API Key', // defaults to process.env["OPENAI_API_KEY"] }); async function main() { diff --git a/src/index.ts b/src/index.ts index 05f41b882..4c6d0ba7a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,10 +9,15 @@ import * as API from 'openai/resources/index'; export interface ClientOptions { /** - * Defaults to process.env["OPENAI_API_KEY"]. + * Defaults to process.env['OPENAI_API_KEY']. */ apiKey?: string; + /** + * Defaults to process.env['OPENAI_ORG_ID']. + */ + organization?: string | null; + /** * Override the default base URL for the API, e.g., "https://api.example.com/v2/" */ @@ -72,21 +77,20 @@ export interface ClientOptions { * Only set this option to `true` if you understand the risks and have appropriate mitigations in place. */ dangerouslyAllowBrowser?: boolean; - - organization?: string | null; } /** API Client for interfacing with the OpenAI API. */ export class OpenAI extends Core.APIClient { apiKey: string; - organization?: string | null; + organization: string | null; private _options: ClientOptions; /** * API Client for interfacing with the OpenAI API. * - * @param {string} [opts.apiKey=process.env['OPENAI_API_KEY']] - The API Key to send to the API. + * @param {string} [opts.apiKey==process.env['OPENAI_API_KEY'] ?? undefined] + * @param {string | null} [opts.organization==process.env['OPENAI_ORG_ID'] ?? null] * @param {string} [opts.baseURL] - Override the default base URL for the API. * @param {number} [opts.timeout=10 minutes] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. * @param {number} [opts.httpAgent] - An HTTP agent used to manage HTTP(s) connections. @@ -95,7 +99,6 @@ export class OpenAI extends Core.APIClient { * @param {Core.Headers} opts.defaultHeaders - Default headers to include with every request to the API. * @param {Core.DefaultQuery} opts.defaultQuery - Default query parameters to include with every request to the API. * @param {boolean} [opts.dangerouslyAllowBrowser=false] - By default, client-side use of this library is not allowed, as it risks exposing your secret API credentials to attackers. - * @param {string | null} [opts.organization] */ constructor({ apiKey = Core.readEnv('OPENAI_API_KEY'), @@ -104,7 +107,7 @@ export class OpenAI extends Core.APIClient { }: ClientOptions = {}) { if (apiKey === undefined) { 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' }).", + "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 API Key' }).", ); } diff --git a/tests/api-resources/audio/transcriptions.test.ts b/tests/api-resources/audio/transcriptions.test.ts index ce0f5e389..cc23c130f 100644 --- a/tests/api-resources/audio/transcriptions.test.ts +++ b/tests/api-resources/audio/transcriptions.test.ts @@ -4,7 +4,7 @@ import OpenAI, { toFile } from 'openai'; import { Response } from 'node-fetch'; const openai = new OpenAI({ - apiKey: 'something1234', + apiKey: 'My API Key', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/audio/translations.test.ts b/tests/api-resources/audio/translations.test.ts index 903c18909..723625f6e 100644 --- a/tests/api-resources/audio/translations.test.ts +++ b/tests/api-resources/audio/translations.test.ts @@ -4,7 +4,7 @@ import OpenAI, { toFile } from 'openai'; import { Response } from 'node-fetch'; const openai = new OpenAI({ - apiKey: 'something1234', + apiKey: 'My API Key', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/chat/completions.test.ts b/tests/api-resources/chat/completions.test.ts index 0725211a8..4a5616cae 100644 --- a/tests/api-resources/chat/completions.test.ts +++ b/tests/api-resources/chat/completions.test.ts @@ -4,7 +4,7 @@ import OpenAI from 'openai'; import { Response } from 'node-fetch'; const openai = new OpenAI({ - apiKey: 'something1234', + apiKey: 'My API Key', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/completions.test.ts b/tests/api-resources/completions.test.ts index c4d09793d..b3f083727 100644 --- a/tests/api-resources/completions.test.ts +++ b/tests/api-resources/completions.test.ts @@ -4,7 +4,7 @@ import OpenAI from 'openai'; import { Response } from 'node-fetch'; const openai = new OpenAI({ - apiKey: 'something1234', + apiKey: 'My API Key', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/edits.test.ts b/tests/api-resources/edits.test.ts index 8acf37951..add95f051 100644 --- a/tests/api-resources/edits.test.ts +++ b/tests/api-resources/edits.test.ts @@ -4,7 +4,7 @@ import OpenAI from 'openai'; import { Response } from 'node-fetch'; const openai = new OpenAI({ - apiKey: 'something1234', + apiKey: 'My API Key', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/embeddings.test.ts b/tests/api-resources/embeddings.test.ts index 23a33b30c..1e2af9297 100644 --- a/tests/api-resources/embeddings.test.ts +++ b/tests/api-resources/embeddings.test.ts @@ -4,7 +4,7 @@ import OpenAI from 'openai'; import { Response } from 'node-fetch'; const openai = new OpenAI({ - apiKey: 'something1234', + apiKey: 'My API Key', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/files.test.ts b/tests/api-resources/files.test.ts index daa70c2ae..dc8a6da00 100644 --- a/tests/api-resources/files.test.ts +++ b/tests/api-resources/files.test.ts @@ -4,7 +4,7 @@ import OpenAI, { toFile } from 'openai'; import { Response } from 'node-fetch'; const openai = new OpenAI({ - apiKey: 'something1234', + apiKey: 'My API Key', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/fine-tunes.test.ts b/tests/api-resources/fine-tunes.test.ts index 1221b6bc6..c82898ff2 100644 --- a/tests/api-resources/fine-tunes.test.ts +++ b/tests/api-resources/fine-tunes.test.ts @@ -4,7 +4,7 @@ import OpenAI from 'openai'; import { Response } from 'node-fetch'; const openai = new OpenAI({ - apiKey: 'something1234', + apiKey: 'My API Key', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/fine-tuning/jobs.test.ts b/tests/api-resources/fine-tuning/jobs.test.ts index 6222a6a3e..9bcb4b085 100644 --- a/tests/api-resources/fine-tuning/jobs.test.ts +++ b/tests/api-resources/fine-tuning/jobs.test.ts @@ -4,7 +4,7 @@ import OpenAI from 'openai'; import { Response } from 'node-fetch'; const openai = new OpenAI({ - apiKey: 'something1234', + apiKey: 'My API Key', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/images.test.ts b/tests/api-resources/images.test.ts index 55a5c22cd..c9291b258 100644 --- a/tests/api-resources/images.test.ts +++ b/tests/api-resources/images.test.ts @@ -4,7 +4,7 @@ import OpenAI, { toFile } from 'openai'; import { Response } from 'node-fetch'; const openai = new OpenAI({ - apiKey: 'something1234', + apiKey: 'My API Key', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/models.test.ts b/tests/api-resources/models.test.ts index d3c06b8a7..91eb0d055 100644 --- a/tests/api-resources/models.test.ts +++ b/tests/api-resources/models.test.ts @@ -4,7 +4,7 @@ import OpenAI from 'openai'; import { Response } from 'node-fetch'; const openai = new OpenAI({ - apiKey: 'something1234', + apiKey: 'My API Key', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/api-resources/moderations.test.ts b/tests/api-resources/moderations.test.ts index 798710ce7..ad315df5d 100644 --- a/tests/api-resources/moderations.test.ts +++ b/tests/api-resources/moderations.test.ts @@ -4,7 +4,7 @@ import OpenAI from 'openai'; import { Response } from 'node-fetch'; const openai = new OpenAI({ - apiKey: 'something1234', + apiKey: 'My API Key', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); diff --git a/tests/index.test.ts b/tests/index.test.ts index bbb4b2135..192d26c6c 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -23,7 +23,7 @@ describe('instantiate client', () => { const client = new OpenAI({ baseURL: 'http://localhost:5000/', defaultHeaders: { 'X-My-Default-Header': '2' }, - apiKey: 'my api key', + apiKey: 'My API Key', }); test('they are used in the request', () => { @@ -55,7 +55,7 @@ describe('instantiate client', () => { const client = new OpenAI({ baseURL: 'http://localhost:5000/', defaultQuery: { apiVersion: 'foo' }, - apiKey: 'my api key', + apiKey: 'My API Key', }); expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo'); }); @@ -64,7 +64,7 @@ describe('instantiate client', () => { const client = new OpenAI({ baseURL: 'http://localhost:5000/', defaultQuery: { apiVersion: 'foo', hello: 'world' }, - apiKey: 'my api key', + apiKey: 'My API Key', }); expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo&hello=world'); }); @@ -73,7 +73,7 @@ describe('instantiate client', () => { const client = new OpenAI({ baseURL: 'http://localhost:5000/', defaultQuery: { hello: 'world' }, - apiKey: 'my api key', + apiKey: 'My API Key', }); expect(client.buildURL('/foo', { hello: undefined })).toEqual('http://localhost:5000/foo'); }); @@ -82,7 +82,7 @@ describe('instantiate client', () => { test('custom fetch', async () => { const client = new OpenAI({ baseURL: 'http://localhost:5000/', - apiKey: 'my api key', + apiKey: 'My API Key', fetch: (url) => { return Promise.resolve( new Response(JSON.stringify({ url, custom: true }), { @@ -99,7 +99,7 @@ describe('instantiate client', () => { test('custom signal', async () => { const client = new OpenAI({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', - apiKey: 'my api key', + apiKey: 'My API Key', fetch: (...args) => { return new Promise((resolve, reject) => setTimeout( @@ -124,57 +124,42 @@ describe('instantiate client', () => { describe('baseUrl', () => { test('trailing slash', () => { - const client = new OpenAI({ baseURL: 'http://localhost:5000/custom/path/', apiKey: 'my api key' }); + const client = new OpenAI({ baseURL: 'http://localhost:5000/custom/path/', apiKey: 'My API Key' }); expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); }); test('no trailing slash', () => { - const client = new OpenAI({ baseURL: 'http://localhost:5000/custom/path', apiKey: 'my api key' }); + const client = new OpenAI({ baseURL: 'http://localhost:5000/custom/path', apiKey: 'My API Key' }); expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); }); }); test('maxRetries option is correctly set', () => { - const client = new OpenAI({ maxRetries: 1, apiKey: 'my api key' }); + const client = new OpenAI({ maxRetries: 1, apiKey: 'My API Key' }); expect(client.maxRetries).toEqual(1); // default - const client2 = new OpenAI({ apiKey: 'my api key' }); + const client2 = new OpenAI({ apiKey: 'My API Key' }); expect(client2.maxRetries).toEqual(2); }); - test('with minimal arguments', () => { - // set API Key via env var - process.env['OPENAI_API_KEY'] = 'env var api key'; + test('with environment variable arguments', () => { + // set options via env var + process.env['OPENAI_API_KEY'] = 'My API Key'; const client = new OpenAI(); - expect(client.apiKey).toBe('env var api key'); + expect(client.apiKey).toBe('My API Key'); }); - test('with apiKey argument', () => { - process.env['OPENAI_API_KEY'] = 'env var api key'; - - const client = new OpenAI({ apiKey: 'another api key' }); - expect(client.apiKey).toBe('another api key'); - }); - - test('with options argument', () => { - process.env['OPENAI_API_KEY'] = 'env var api key'; - - // apiKey - const client = new OpenAI({ apiKey: 'my api key' }); - expect(client.apiKey).toBe('my api key'); - }); - - test('with disabled authentication', () => { - // fails if no API Key provided - expect(() => { - new OpenAI(); - }).toThrow(); + test('with overriden environment variable arguments', () => { + // set options via env var + process.env['OPENAI_API_KEY'] = 'another My API Key'; + const client = new OpenAI({ apiKey: 'My API Key' }); + expect(client.apiKey).toBe('My API Key'); }); }); describe('request building', () => { - const client = new OpenAI({ apiKey: 'my api key' }); + const client = new OpenAI({ apiKey: 'My API Key' }); describe('Content-Length', () => { test('handles multi-byte characters', () => { @@ -200,7 +185,7 @@ describe('retries', () => { return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; - const client = new OpenAI({ apiKey: 'my api key', timeout: 2000, fetch: testFetch }); + const client = new OpenAI({ apiKey: 'My API Key', timeout: 2000, fetch: testFetch }); expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); expect(count).toEqual(2); From 6130145f105f8917cc71e271d9a49909599fc847 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Thu, 12 Oct 2023 17:55:26 +0100 Subject: [PATCH 04/12] ci: before first major release use a minor version update for feature changes (#372) --- release-please-config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-please-config.json b/release-please-config.json index e5d1018e1..26eb693f3 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -6,7 +6,7 @@ "include-v-in-tag": true, "include-component-in-tag": false, "bump-minor-pre-major": true, - "bump-patch-for-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, "pull-request-header": "Automated Release PR", "pull-request-title-pattern": "release: ${version}", "changelog-sections": [ From b088998ae610de54bb8700eefd6b664eb9a2fcc3 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Thu, 12 Oct 2023 23:57:35 +0100 Subject: [PATCH 05/12] chore: add case insensitive get header function (#373) --- src/core.ts | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/core.ts b/src/core.ts index 73b2f5574..62d2ac212 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1059,16 +1059,33 @@ export const isHeadersProtocol = (headers: any): headers is HeadersProtocol => { return typeof headers?.get === 'function'; }; -export const getHeader = (headers: HeadersLike, key: string): string | null | undefined => { - const lowerKey = key.toLowerCase(); - if (isHeadersProtocol(headers)) return headers.get(key) || headers.get(lowerKey); - const value = headers[key] || headers[lowerKey]; - if (Array.isArray(value)) { - if (value.length <= 1) return value[0]; - console.warn(`Received ${value.length} entries for the ${key} header, using the first entry.`); - return value[0]; +export const getRequiredHeader = (headers: HeadersLike, header: string): string => { + const lowerCasedHeader = header.toLowerCase(); + if (isHeadersProtocol(headers)) { + // to deal with the case where the header looks like Finch-Event-Id + const intercapsHeader = + header[0]?.toUpperCase() + + header.substring(1).replace(/([^\w])(\w)/g, (_m, g1, g2) => g1 + g2.toUpperCase()); + for (const key of [header, lowerCasedHeader, header.toUpperCase(), intercapsHeader]) { + const value = headers.get(key); + if (value) { + return value; + } + } } - return value; + + for (const [key, value] of Object.entries(headers)) { + if (key.toLowerCase() === lowerCasedHeader) { + if (Array.isArray(value)) { + if (value.length <= 1) return value[0]; + console.warn(`Received ${value.length} entries for the ${header} header, using the first entry.`); + return value[0]; + } + return value; + } + } + + throw new Error(`Could not find ${header} header`); }; /** From a06c6850bfdd756dc8f07dd1f70218be610faa30 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Fri, 13 Oct 2023 15:50:00 +0100 Subject: [PATCH 06/12] chore: update comment (#376) --- src/core.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core.ts b/src/core.ts index 62d2ac212..4120de182 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1062,7 +1062,7 @@ export const isHeadersProtocol = (headers: any): headers is HeadersProtocol => { export const getRequiredHeader = (headers: HeadersLike, header: string): string => { const lowerCasedHeader = header.toLowerCase(); if (isHeadersProtocol(headers)) { - // to deal with the case where the header looks like Finch-Event-Id + // to deal with the case where the header looks like Stainless-Event-Id const intercapsHeader = header[0]?.toUpperCase() + header.substring(1).replace(/([^\w])(\w)/g, (_m, g1, g2) => g1 + g2.toUpperCase()); From 09233b1ccc80ee900be19050f438cc8aa9dbb513 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Fri, 13 Oct 2023 19:44:37 +0100 Subject: [PATCH 07/12] fix(client): correctly handle errors during streaming (#377) --- src/streaming.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/streaming.ts b/src/streaming.ts index f69724d64..33f641888 100644 --- a/src/streaming.ts +++ b/src/streaming.ts @@ -1,6 +1,8 @@ import { ReadableStream, type Response } from './_shims/index'; import { OpenAIError } from './error'; +import { APIError } from 'openai/error'; + type Bytes = string | ArrayBuffer | Uint8Array | Buffer | null | undefined; type ServerSentEvent = { @@ -58,13 +60,21 @@ export class Stream implements AsyncIterable { } if (sse.event === null) { + let data; + try { - yield JSON.parse(sse.data); + data = JSON.parse(sse.data); } catch (e) { console.error(`Could not parse message into JSON:`, sse.data); console.error(`From chunk:`, sse.raw); throw e; } + + if (data && data.error) { + throw new APIError(undefined, data.error, undefined, undefined); + } + + yield data; } } done = true; From b04031d19210a66f82c7d233a50f7bc427a1bf92 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Sat, 14 Oct 2023 17:04:26 +0100 Subject: [PATCH 08/12] chore: update comment (#378) --- src/streaming.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/streaming.ts b/src/streaming.ts index 33f641888..f69724d64 100644 --- a/src/streaming.ts +++ b/src/streaming.ts @@ -1,8 +1,6 @@ import { ReadableStream, type Response } from './_shims/index'; import { OpenAIError } from './error'; -import { APIError } from 'openai/error'; - type Bytes = string | ArrayBuffer | Uint8Array | Buffer | null | undefined; type ServerSentEvent = { @@ -60,21 +58,13 @@ export class Stream implements AsyncIterable { } if (sse.event === null) { - let data; - try { - data = JSON.parse(sse.data); + yield JSON.parse(sse.data); } catch (e) { console.error(`Could not parse message into JSON:`, sse.data); console.error(`From chunk:`, sse.raw); throw e; } - - if (data && data.error) { - throw new APIError(undefined, data.error, undefined, undefined); - } - - yield data; } } done = true; From 9ced5804777a5857d6775a49ddf30ed9cc016fab Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Mon, 16 Oct 2023 11:13:17 +0100 Subject: [PATCH 09/12] fix(client): correctly handle errors during streaming (#379) --- src/streaming.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/streaming.ts b/src/streaming.ts index f69724d64..33f641888 100644 --- a/src/streaming.ts +++ b/src/streaming.ts @@ -1,6 +1,8 @@ import { ReadableStream, type Response } from './_shims/index'; import { OpenAIError } from './error'; +import { APIError } from 'openai/error'; + type Bytes = string | ArrayBuffer | Uint8Array | Buffer | null | undefined; type ServerSentEvent = { @@ -58,13 +60,21 @@ export class Stream implements AsyncIterable { } if (sse.event === null) { + let data; + try { - yield JSON.parse(sse.data); + data = JSON.parse(sse.data); } catch (e) { console.error(`Could not parse message into JSON:`, sse.data); console.error(`From chunk:`, sse.raw); throw e; } + + if (data && data.error) { + throw new APIError(undefined, data.error, undefined, undefined); + } + + yield data; } } done = true; From 689db0b8058527ae5c3af5e457c962d8a6635297 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Mon, 16 Oct 2023 19:16:47 +0100 Subject: [PATCH 10/12] chore(internal): add debug logs for stream responses (#380) --- src/core.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core.ts b/src/core.ts index 4120de182..9cd99639d 100644 --- a/src/core.ts +++ b/src/core.ts @@ -42,6 +42,8 @@ type APIResponseProps = { async function defaultParseResponse(props: APIResponseProps): Promise { const { response } = props; if (props.options.stream) { + debug('response', response.status, response.url, response.headers, response.body); + // Note: there is an invariant here that isn't represented in the type system // that if you set `stream: true` the response type must also be `Stream` return Stream.fromSSEResponse(response, props.controller) as any; From 68dfb17cce300ade8d29afc854d616833b3283ca Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Mon, 16 Oct 2023 21:29:07 +0100 Subject: [PATCH 11/12] fix: improve status code in error messages (#381) --- src/error.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/error.ts b/src/error.ts index 28a8b540f..33bc5f06c 100644 --- a/src/error.ts +++ b/src/error.ts @@ -31,14 +31,23 @@ export class APIError extends OpenAIError { } private static makeMessage(status: number | undefined, error: any, message: string | undefined) { - return ( - (status || '') + - (error?.message ? + const msg = + error?.message ? typeof error.message === 'string' ? error.message : JSON.stringify(error.message) : error ? JSON.stringify(error) - : message || 'status code (no body)') - ); + : message; + + if (status && msg) { + return `${status} ${msg}`; + } + if (status) { + return `${status} status code (no body)`; + } + if (msg) { + return msg; + } + return '(no status code or body)'; } static generate( From 09e10b3cfd6365a54a86096ccf2ab9dea6d0dc5a Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Mon, 16 Oct 2023 21:29:26 +0100 Subject: [PATCH 12/12] release: 4.12.2 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 25 +++++++++++++++++++++++++ package.json | 2 +- src/version.ts | 2 +- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 130086b59..f6b69126a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.12.1" + ".": "4.12.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 940362e44..e75f10c7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## 4.12.2 (2023-10-16) + +Full Changelog: [v4.12.1...v4.12.2](https://github.com/openai/openai-node/compare/v4.12.1...v4.12.2) + +### Bug Fixes + +* **client:** correctly handle errors during streaming ([#377](https://github.com/openai/openai-node/issues/377)) ([09233b1](https://github.com/openai/openai-node/commit/09233b1ccc80ee900be19050f438cc8aa9dbb513)) +* **client:** correctly handle errors during streaming ([#379](https://github.com/openai/openai-node/issues/379)) ([9ced580](https://github.com/openai/openai-node/commit/9ced5804777a5857d6775a49ddf30ed9cc016fab)) +* improve status code in error messages ([#381](https://github.com/openai/openai-node/issues/381)) ([68dfb17](https://github.com/openai/openai-node/commit/68dfb17cce300ade8d29afc854d616833b3283ca)) + + +### Chores + +* add case insensitive get header function ([#373](https://github.com/openai/openai-node/issues/373)) ([b088998](https://github.com/openai/openai-node/commit/b088998ae610de54bb8700eefd6b664eb9a2fcc3)) +* **internal:** add debug logs for stream responses ([#380](https://github.com/openai/openai-node/issues/380)) ([689db0b](https://github.com/openai/openai-node/commit/689db0b8058527ae5c3af5e457c962d8a6635297)) +* show deprecation notice on re-export ([#368](https://github.com/openai/openai-node/issues/368)) ([b176703](https://github.com/openai/openai-node/commit/b176703102998f0e9d8ca2ed93ccd495fd10a6ee)) +* update comment ([#376](https://github.com/openai/openai-node/issues/376)) ([a06c685](https://github.com/openai/openai-node/commit/a06c6850bfdd756dc8f07dd1f70218be610faa30)) +* update comment ([#378](https://github.com/openai/openai-node/issues/378)) ([b04031d](https://github.com/openai/openai-node/commit/b04031d19210a66f82c7d233a50f7bc427a1bf92)) + + +### Refactors + +* **streaming:** change Stream constructor signature ([#370](https://github.com/openai/openai-node/issues/370)) ([71984ed](https://github.com/openai/openai-node/commit/71984edc3141ba99ffa1327bab6a182b4452209f)) +* **test:** refactor authentication tests ([#371](https://github.com/openai/openai-node/issues/371)) ([e0d459f](https://github.com/openai/openai-node/commit/e0d459f958451a99e15a11a0e5ea6471abbe1ac1)) + ## 4.12.1 (2023-10-11) Full Changelog: [v4.12.0...v4.12.1](https://github.com/openai/openai-node/compare/v4.12.0...v4.12.1) diff --git a/package.json b/package.json index 2e728d452..cdb3fce92 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openai", - "version": "4.12.1", + "version": "4.12.2", "description": "Client library for the OpenAI API", "author": "OpenAI ", "types": "dist/index.d.ts", diff --git a/src/version.ts b/src/version.ts index cec55e2a7..df9635d1c 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '4.12.1'; // x-release-please-version +export const VERSION = '4.12.2'; // x-release-please-version