Skip to content

Commit f35bbc5

Browse files
committedJul 8, 2023
v4.0.0-beta.3
1 parent 492cc2e commit f35bbc5

23 files changed

+449
-184
lines changed
 

‎api.md

+43-43
Original file line numberDiff line numberDiff line change
@@ -2,133 +2,133 @@
22

33
Types:
44

5-
- <code><a href="./resources/completions.ts">Completion</a></code>
6-
- <code><a href="./resources/completions.ts">CompletionChoice</a></code>
5+
- <code><a href="./src/resources/completions.ts">Completion</a></code>
6+
- <code><a href="./src/resources/completions.ts">CompletionChoice</a></code>
77

88
Methods:
99

10-
- <code title="post /completions">client.completions.<a href="./resources/completions.ts">create</a>({ ...params }) -> Completion</code>
10+
- <code title="post /completions">client.completions.<a href="./src/resources/completions.ts">create</a>({ ...params }) -> Completion</code>
1111

1212
# Chat
1313

1414
## Completions
1515

1616
Types:
1717

18-
- <code><a href="./resources/chat/completions.ts">ChatCompletion</a></code>
19-
- <code><a href="./resources/chat/completions.ts">ChatCompletionChunk</a></code>
18+
- <code><a href="./src/resources/chat/completions.ts">ChatCompletion</a></code>
19+
- <code><a href="./src/resources/chat/completions.ts">ChatCompletionChunk</a></code>
2020

2121
Methods:
2222

23-
- <code title="post /chat/completions">client.chat.completions.<a href="./resources/chat/completions.ts">create</a>({ ...params }) -> ChatCompletion</code>
23+
- <code title="post /chat/completions">client.chat.completions.<a href="./src/resources/chat/completions.ts">create</a>({ ...params }) -> ChatCompletion</code>
2424

2525
# Edits
2626

2727
Types:
2828

29-
- <code><a href="./resources/edits.ts">Edit</a></code>
29+
- <code><a href="./src/resources/edits.ts">Edit</a></code>
3030

3131
Methods:
3232

33-
- <code title="post /edits">client.edits.<a href="./resources/edits.ts">create</a>({ ...params }) -> Edit</code>
33+
- <code title="post /edits">client.edits.<a href="./src/resources/edits.ts">create</a>({ ...params }) -> Edit</code>
3434

3535
# Embeddings
3636

3737
Types:
3838

39-
- <code><a href="./resources/embeddings.ts">Embedding</a></code>
39+
- <code><a href="./src/resources/embeddings.ts">Embedding</a></code>
4040

4141
Methods:
4242

43-
- <code title="post /embeddings">client.embeddings.<a href="./resources/embeddings.ts">create</a>({ ...params }) -> Embedding</code>
43+
- <code title="post /embeddings">client.embeddings.<a href="./src/resources/embeddings.ts">create</a>({ ...params }) -> Embedding</code>
4444

4545
# Files
4646

4747
Types:
4848

49-
- <code><a href="./resources/files.ts">FileContent</a></code>
50-
- <code><a href="./resources/files.ts">FileDeleted</a></code>
51-
- <code><a href="./resources/files.ts">FileObject</a></code>
49+
- <code><a href="./src/resources/files.ts">FileContent</a></code>
50+
- <code><a href="./src/resources/files.ts">FileDeleted</a></code>
51+
- <code><a href="./src/resources/files.ts">FileObject</a></code>
5252

5353
Methods:
5454

55-
- <code title="post /files">client.files.<a href="./resources/files.ts">create</a>({ ...params }) -> FileObject</code>
56-
- <code title="get /files/{file_id}">client.files.<a href="./resources/files.ts">retrieve</a>(fileId) -> FileObject</code>
57-
- <code title="get /files">client.files.<a href="./resources/files.ts">list</a>() -> FileObjectsPage</code>
58-
- <code title="delete /files/{file_id}">client.files.<a href="./resources/files.ts">del</a>(fileId) -> FileDeleted</code>
59-
- <code title="get /files/{file_id}/content">client.files.<a href="./resources/files.ts">retrieveFileContent</a>(fileId) -> string</code>
55+
- <code title="post /files">client.files.<a href="./src/resources/files.ts">create</a>({ ...params }) -> FileObject</code>
56+
- <code title="get /files/{file_id}">client.files.<a href="./src/resources/files.ts">retrieve</a>(fileId) -> FileObject</code>
57+
- <code title="get /files">client.files.<a href="./src/resources/files.ts">list</a>() -> FileObjectsPage</code>
58+
- <code title="delete /files/{file_id}">client.files.<a href="./src/resources/files.ts">del</a>(fileId) -> FileDeleted</code>
59+
- <code title="get /files/{file_id}/content">client.files.<a href="./src/resources/files.ts">retrieveFileContent</a>(fileId) -> string</code>
6060

6161
# Images
6262

6363
Types:
6464

65-
- <code><a href="./resources/images.ts">Image</a></code>
66-
- <code><a href="./resources/images.ts">ImagesResponse</a></code>
65+
- <code><a href="./src/resources/images.ts">Image</a></code>
66+
- <code><a href="./src/resources/images.ts">ImagesResponse</a></code>
6767

6868
Methods:
6969

70-
- <code title="post /images/variations">client.images.<a href="./resources/images.ts">createVariation</a>({ ...params }) -> ImagesResponse</code>
71-
- <code title="post /images/edits">client.images.<a href="./resources/images.ts">edit</a>({ ...params }) -> ImagesResponse</code>
72-
- <code title="post /images/generations">client.images.<a href="./resources/images.ts">generate</a>({ ...params }) -> ImagesResponse</code>
70+
- <code title="post /images/variations">client.images.<a href="./src/resources/images.ts">createVariation</a>({ ...params }) -> ImagesResponse</code>
71+
- <code title="post /images/edits">client.images.<a href="./src/resources/images.ts">edit</a>({ ...params }) -> ImagesResponse</code>
72+
- <code title="post /images/generations">client.images.<a href="./src/resources/images.ts">generate</a>({ ...params }) -> ImagesResponse</code>
7373

7474
# Audio
7575

7676
## Transcriptions
7777

7878
Types:
7979

80-
- <code><a href="./resources/audio/transcriptions.ts">Transcription</a></code>
80+
- <code><a href="./src/resources/audio/transcriptions.ts">Transcription</a></code>
8181

8282
Methods:
8383

84-
- <code title="post /audio/transcriptions">client.audio.transcriptions.<a href="./resources/audio/transcriptions.ts">create</a>({ ...params }) -> Transcription</code>
84+
- <code title="post /audio/transcriptions">client.audio.transcriptions.<a href="./src/resources/audio/transcriptions.ts">create</a>({ ...params }) -> Transcription</code>
8585

8686
## Translations
8787

8888
Types:
8989

90-
- <code><a href="./resources/audio/translations.ts">Translation</a></code>
90+
- <code><a href="./src/resources/audio/translations.ts">Translation</a></code>
9191

9292
Methods:
9393

94-
- <code title="post /audio/translations">client.audio.translations.<a href="./resources/audio/translations.ts">create</a>({ ...params }) -> Translation</code>
94+
- <code title="post /audio/translations">client.audio.translations.<a href="./src/resources/audio/translations.ts">create</a>({ ...params }) -> Translation</code>
9595

9696
# Moderations
9797

9898
Types:
9999

100-
- <code><a href="./resources/moderations.ts">Moderation</a></code>
101-
- <code><a href="./resources/moderations.ts">ModerationCreateResponse</a></code>
100+
- <code><a href="./src/resources/moderations.ts">Moderation</a></code>
101+
- <code><a href="./src/resources/moderations.ts">ModerationCreateResponse</a></code>
102102

103103
Methods:
104104

105-
- <code title="post /moderations">client.moderations.<a href="./resources/moderations.ts">create</a>({ ...params }) -> ModerationCreateResponse</code>
105+
- <code title="post /moderations">client.moderations.<a href="./src/resources/moderations.ts">create</a>({ ...params }) -> ModerationCreateResponse</code>
106106

107107
# Models
108108

109109
Types:
110110

111-
- <code><a href="./resources/models.ts">Model</a></code>
112-
- <code><a href="./resources/models.ts">ModelDeleted</a></code>
111+
- <code><a href="./src/resources/models.ts">Model</a></code>
112+
- <code><a href="./src/resources/models.ts">ModelDeleted</a></code>
113113

114114
Methods:
115115

116-
- <code title="get /models/{model}">client.models.<a href="./resources/models.ts">retrieve</a>(model) -> Model</code>
117-
- <code title="get /models">client.models.<a href="./resources/models.ts">list</a>() -> ModelsPage</code>
118-
- <code title="delete /models/{model}">client.models.<a href="./resources/models.ts">del</a>(model) -> ModelDeleted</code>
116+
- <code title="get /models/{model}">client.models.<a href="./src/resources/models.ts">retrieve</a>(model) -> Model</code>
117+
- <code title="get /models">client.models.<a href="./src/resources/models.ts">list</a>() -> ModelsPage</code>
118+
- <code title="delete /models/{model}">client.models.<a href="./src/resources/models.ts">del</a>(model) -> ModelDeleted</code>
119119

120120
# FineTunes
121121

122122
Types:
123123

124-
- <code><a href="./resources/fine-tunes.ts">FineTune</a></code>
125-
- <code><a href="./resources/fine-tunes.ts">FineTuneEvent</a></code>
126-
- <code><a href="./resources/fine-tunes.ts">FineTuneEventsListResponse</a></code>
124+
- <code><a href="./src/resources/fine-tunes.ts">FineTune</a></code>
125+
- <code><a href="./src/resources/fine-tunes.ts">FineTuneEvent</a></code>
126+
- <code><a href="./src/resources/fine-tunes.ts">FineTuneEventsListResponse</a></code>
127127

128128
Methods:
129129

130-
- <code title="post /fine-tunes">client.fineTunes.<a href="./resources/fine-tunes.ts">create</a>({ ...params }) -> FineTune</code>
131-
- <code title="get /fine-tunes/{fine_tune_id}">client.fineTunes.<a href="./resources/fine-tunes.ts">retrieve</a>(fineTuneId) -> FineTune</code>
132-
- <code title="get /fine-tunes">client.fineTunes.<a href="./resources/fine-tunes.ts">list</a>() -> FineTunesPage</code>
133-
- <code title="post /fine-tunes/{fine_tune_id}/cancel">client.fineTunes.<a href="./resources/fine-tunes.ts">cancel</a>(fineTuneId) -> FineTune</code>
134-
- <code title="get /fine-tunes/{fine_tune_id}/events">client.fineTunes.<a href="./resources/fine-tunes.ts">listEvents</a>(fineTuneId, { ...params }) -> FineTuneEventsListResponse</code>
130+
- <code title="post /fine-tunes">client.fineTunes.<a href="./src/resources/fine-tunes.ts">create</a>({ ...params }) -> FineTune</code>
131+
- <code title="get /fine-tunes/{fine_tune_id}">client.fineTunes.<a href="./src/resources/fine-tunes.ts">retrieve</a>(fineTuneId) -> FineTune</code>
132+
- <code title="get /fine-tunes">client.fineTunes.<a href="./src/resources/fine-tunes.ts">list</a>() -> FineTunesPage</code>
133+
- <code title="post /fine-tunes/{fine_tune_id}/cancel">client.fineTunes.<a href="./src/resources/fine-tunes.ts">cancel</a>(fineTuneId) -> FineTune</code>
134+
- <code title="get /fine-tunes/{fine_tune_id}/events">client.fineTunes.<a href="./src/resources/fine-tunes.ts">listEvents</a>(fineTuneId, { ...params }) -> FineTuneEventsListResponse</code>

‎build

+7-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ set -exuo pipefail
33

44
node scripts/check-version.cjs
55

6+
yarn tsc
7+
68
# Build into dist and will publish the package from there,
79
# so that src/resources/foo.ts becomes <package root>/resources/foo.js
810
# This way importing from `"openai/resources/foo"` works
@@ -12,7 +14,10 @@ rm -rf dist
1214
mkdir dist
1315
# Copy src to dist/src and build from dist/src into dist, so that
1416
# the source map for index.js.map will refer to ./src/index.ts etc
15-
cp -rp src dist
17+
cp -rp src README.md dist
18+
for file in LICENSE CHANGELOG.md; do
19+
if [ -e "${file}" ]; then cp "${file}" dist; fi
20+
done
1621
# this converts the export map paths for the dist directory
1722
# and does a few other minor things
1823
node scripts/make-dist-package-json.cjs > dist/package.json
@@ -21,7 +26,7 @@ node scripts/make-dist-package-json.cjs > dist/package.json
2126
tsc-multi
2227
# copy over handwritten .js/.mjs/.d.ts files
2328
cp src/_shims/*.{d.ts,js,mjs} dist/_shims
24-
tsc-alias -p tsconfig.json --resolve-full-paths
29+
tsc-alias -p tsconfig.build.json
2530
# we need to add exports = module.exports = OpenAI Node to index.js;
2631
# No way to get that from index.ts because it would cause compile errors
2732
# when building .mjs

‎ecosystem-tests/vercel-edge/package-lock.json

+7-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎ecosystem-tests/vercel-edge/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"react-dom": "18.2.0"
1818
},
1919
"devDependencies": {
20-
"@types/node": "20.3.1",
20+
"@types/node": "20.3.3",
2121
"@types/react": "18.2.13",
2222
"@types/react-dom": "18.2.6",
2323
"edge-runtime": "^2.4.3",

‎examples/azure.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env yarn tsn -T
2+
3+
import OpenAI from 'openai';
4+
5+
// The name of your Azure OpenAI Resource.
6+
// https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal#create-a-resource
7+
const resource = '<your resource name>';
8+
9+
// Corresponds to your Model deployment within your OpenAI resource, e.g. my-gpt35-16k-deployment
10+
// Navigate to the Azure OpenAI Studio to deploy a model.
11+
const model = '<your model>';
12+
13+
const apiKey = process.env['AZURE_OPENAI_API_KEY'];
14+
if (!apiKey) {
15+
throw new Error('The AZURE_OPENAI_API_KEY environment variable is missing or empty.');
16+
}
17+
18+
// Azure OpenAI requires a custom baseURL, api-version query param, and api-key header.
19+
const openai = new OpenAI({
20+
apiKey,
21+
baseURL: `https://${resource}.openai.azure.com/openai/deployments/${model}`,
22+
defaultQuery: { 'api-version': '2023-06-01-preview' },
23+
defaultHeaders: { 'api-key': apiKey },
24+
});
25+
26+
async function main() {
27+
console.log('Non-streaming:');
28+
const result = await openai.chat.completions.create({
29+
model,
30+
messages: [{ role: 'user', content: 'Say hello!' }],
31+
});
32+
console.log(result.choices[0]!.message?.content);
33+
34+
console.log();
35+
console.log('Streaming:');
36+
const stream = await openai.chat.completions.create({
37+
model,
38+
messages: [{ role: 'user', content: 'Say hello!' }],
39+
stream: true,
40+
});
41+
42+
for await (const part of stream) {
43+
process.stdout.write(part.choices[0]?.delta?.content ?? '');
44+
}
45+
process.stdout.write('\n');
46+
}
47+
48+
main().catch(console.error);

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "openai",
3-
"version": "4.0.0-beta.2",
3+
"version": "4.0.0-beta.3",
44
"description": "Client library for the OpenAI API",
55
"author": "OpenAI <support@openai.com>",
66
"types": "dist/index.d.ts",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
Object.defineProperty(exports, '__esModule', { value: true });
3+
4+
const path = require('path');
5+
const distSrcDir = path.resolve(__dirname, '..', 'dist', 'src');
6+
7+
function replaceSelfReferencingImports({ orig, file, config }) {
8+
// replace self-referencing imports in source files to reduce errors users will
9+
// see if they go to definition
10+
if (!file.startsWith(distSrcDir)) return orig;
11+
return orig.replace(/['"]([^"'\r\n]+)['"]/, (match, importPath) => {
12+
if (!importPath.startsWith('openai/')) return match;
13+
let relativePath = path.relative(
14+
path.dirname(file),
15+
path.join(distSrcDir, importPath.substring('openai/'.length)),
16+
);
17+
if (!relativePath.startsWith('.')) relativePath = `./${relativePath}`;
18+
return JSON.stringify(relativePath);
19+
});
20+
}
21+
exports.default = replaceSelfReferencingImports;

‎src/core.ts

+19-2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ export abstract class APIClient {
7373
};
7474
}
7575

76+
protected abstract defaultQuery(): DefaultQuery | undefined;
77+
7678
/**
7779
* Override this to add your own headers validation:
7880
*/
@@ -130,9 +132,17 @@ export abstract class APIClient {
130132
const contentLength = typeof body === 'string' ? body.length.toString() : null;
131133

132134
const url = this.buildURL(path!, query);
133-
const httpAgent = options.httpAgent ?? this.httpAgent ?? getDefaultAgent(url);
135+
if ('timeout' in options) validatePositiveInteger('timeout', options.timeout);
134136
const timeout = options.timeout ?? this.timeout;
135-
validatePositiveInteger('timeout', timeout);
137+
const httpAgent = options.httpAgent ?? this.httpAgent ?? getDefaultAgent(url);
138+
const minAgentTimeout = timeout + 1000;
139+
if ((httpAgent as any)?.options && minAgentTimeout > ((httpAgent as any).options.timeout ?? 0)) {
140+
// Allow any given request to bump our agent active socket timeout.
141+
// This may seem strange, but leaking active sockets should be rare and not particularly problematic,
142+
// and without mutating agent we would need to create more of them.
143+
// This tradeoff optimizes for performance.
144+
(httpAgent as any).options.timeout = minAgentTimeout;
145+
}
136146

137147
if (this.idempotencyHeader && method !== 'get') {
138148
if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey();
@@ -260,6 +270,11 @@ export abstract class APIClient {
260270
new URL(path)
261271
: new URL(this.baseURL + (this.baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path));
262272

273+
const defaultQuery = this.defaultQuery();
274+
if (!isEmptyObj(defaultQuery)) {
275+
query = { ...defaultQuery, ...query } as Req;
276+
}
277+
263278
if (query) {
264279
url.search = qs.stringify(query, this.qsOptions());
265280
}
@@ -516,6 +531,7 @@ type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
516531

517532
export type RequestClient = { fetch: Fetch };
518533
export type Headers = Record<string, string | null | undefined>;
534+
export type DefaultQuery = Record<string, string | undefined>;
519535
export type KeysEnum<T> = { [P in keyof Required<T>]: true };
520536

521537
export type RequestOptions<Req extends {} = Record<string, unknown> | Readable> = {
@@ -564,6 +580,7 @@ export type FinalRequestOptions<Req extends {} = Record<string, unknown> | Reada
564580
};
565581

566582
export type APIResponse<T> = T & {
583+
/** @deprecated - we plan to add a different way to access raw response information shortly. */
567584
responseHeaders: Headers;
568585
};
569586

‎src/index.ts

+46-1
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,52 @@ type Config = {
1313
* Defaults to process.env["OPENAI_API_KEY"].
1414
*/
1515
apiKey?: string;
16+
17+
/**
18+
* Override the default base URL for the API, e.g., "https://api.example.com/v2/"
19+
*/
1620
baseURL?: string;
21+
22+
/**
23+
* The maximum amount of time (in milliseconds) that the client should wait for a response
24+
* from the server before timing out a single request.
25+
*
26+
* Note that request timeouts are retried by default, so in a worst-case scenario you may wait
27+
* much longer than this timeout before the promise succeeds or fails.
28+
*/
1729
timeout?: number;
30+
31+
/**
32+
* An HTTP agent used to manage HTTP(S) connections.
33+
*
34+
* If not provided, an agent will be constructed by default in the Node.js environment,
35+
* otherwise no agent is used.
36+
*/
1837
httpAgent?: Agent;
38+
39+
/**
40+
* The maximum number of times that the client will retry a request in case of a
41+
* temporary failure, like a network error or a 5XX error from the server.
42+
*
43+
* @default 2
44+
*/
1945
maxRetries?: number;
46+
47+
/**
48+
* Default headers to include with every request to the API.
49+
*
50+
* These can be removed in individual requests by explicitly setting the
51+
* header to `undefined` or `null` in request options.
52+
*/
2053
defaultHeaders?: Core.Headers;
54+
55+
/**
56+
* Default query parameters to include with every request to the API.
57+
*
58+
* These can be removed in individual requests by explicitly setting the
59+
* param to `undefined` in request options.
60+
*/
61+
defaultQuery?: Core.DefaultQuery;
2162
};
2263

2364
/** Instantiate the API Client. */
@@ -41,7 +82,7 @@ export class OpenAI extends Core.APIClient {
4182

4283
super({
4384
baseURL: options.baseURL!,
44-
timeout: options.timeout,
85+
timeout: options.timeout ?? 600000,
4586
httpAgent: options.httpAgent,
4687
maxRetries: options.maxRetries,
4788
});
@@ -60,6 +101,10 @@ export class OpenAI extends Core.APIClient {
60101
models: API.Models = new API.Models(this);
61102
fineTunes: API.FineTunes = new API.FineTunes(this);
62103

104+
protected override defaultQuery(): Core.DefaultQuery | undefined {
105+
return this._options.defaultQuery;
106+
}
107+
63108
protected override defaultHeaders(): Core.Headers {
64109
return {
65110
...super.defaultHeaders(),

‎src/resources/chat/completions.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ export namespace CompletionCreateParams {
229229
* increase likelihood of selection; values like -100 or 100 should result in a ban
230230
* or exclusive selection of the relevant token.
231231
*/
232-
logit_bias?: unknown | null;
232+
logit_bias?: Record<string, number> | null;
233233

234234
/**
235235
* The maximum number of [tokens](/tokenizer) to generate in the chat completion.
@@ -451,7 +451,7 @@ export namespace CompletionCreateParams {
451451
* increase likelihood of selection; values like -100 or 100 should result in a ban
452452
* or exclusive selection of the relevant token.
453453
*/
454-
logit_bias?: unknown | null;
454+
logit_bias?: Record<string, number> | null;
455455

456456
/**
457457
* The maximum number of [tokens](/tokenizer) to generate in the chat completion.

‎src/resources/completions.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export namespace CompletionChoice {
6767

6868
tokens?: Array<string>;
6969

70-
top_logprobs?: Array<unknown>;
70+
top_logprobs?: Array<Record<string, number>>;
7171
}
7272
}
7373

@@ -145,7 +145,7 @@ export namespace CompletionCreateParams {
145145
* As an example, you can pass `{"50256": -100}` to prevent the <|endoftext|> token
146146
* from being generated.
147147
*/
148-
logit_bias?: unknown | null;
148+
logit_bias?: Record<string, number> | null;
149149

150150
/**
151151
* Include the log probabilities on the `logprobs` most likely tokens, as well the
@@ -310,7 +310,7 @@ export namespace CompletionCreateParams {
310310
* As an example, you can pass `{"50256": -100}` to prevent the <|endoftext|> token
311311
* from being generated.
312312
*/
313-
logit_bias?: unknown | null;
313+
logit_bias?: Record<string, number> | null;
314314

315315
/**
316316
* Include the log probabilities on the `logprobs` most likely tokens, as well the

‎src/resources/edits.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import * as API from './';
77
export class Edits extends APIResource {
88
/**
99
* Creates a new edit for the provided input, instruction, and parameters.
10+
*
11+
* @deprecated The Edits API is deprecated; please use Chat Completions instead.
12+
*
13+
* https://openai.com/blog/gpt-4-api-general-availability#deprecation-of-the-edits-api
1014
*/
1115
create(body: EditCreateParams, options?: Core.RequestOptions): Promise<Core.APIResponse<Edit>> {
1216
return this.post('/edits', { body, ...options });
@@ -42,7 +46,7 @@ export namespace Edit {
4246

4347
tokens?: Array<string>;
4448

45-
top_logprobs?: Array<unknown>;
49+
top_logprobs?: Array<Record<string, number>>;
4650
}
4751
}
4852

‎src/resources/files.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export class Files extends APIResource {
5353
* Note: no pagination actually occurs yet, this is for forwards-compatibility.
5454
*/
5555
export class FileObjectsPage extends Page<FileObject> {}
56+
// alias so we can export it in the namespace
57+
type _FileObjectsPage = FileObjectsPage;
5658

5759
export type FileContent = string;
5860

@@ -79,7 +81,7 @@ export interface FileObject {
7981

8082
status?: string;
8183

82-
status_details?: unknown | null;
84+
status_details?: string | null;
8385
}
8486

8587
export interface FileCreateParams {
@@ -106,6 +108,6 @@ export namespace Files {
106108
export import FileContent = API.FileContent;
107109
export import FileDeleted = API.FileDeleted;
108110
export import FileObject = API.FileObject;
109-
export import FileObjectsPage = API.FileObjectsPage;
111+
export type FileObjectsPage = _FileObjectsPage;
110112
export import FileCreateParams = API.FileCreateParams;
111113
}

‎src/resources/fine-tunes.ts

+23-2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export class FineTunes extends APIResource {
6363
): Promise<Core.APIResponse<FineTuneEventsListResponse | Stream<FineTuneEvent>>> {
6464
return this.get(`/fine-tunes/${fineTuneId}/events`, {
6565
query,
66+
timeout: 86400000,
6667
...options,
6768
stream: query?.stream ?? false,
6869
});
@@ -73,6 +74,8 @@ export class FineTunes extends APIResource {
7374
* Note: no pagination actually occurs yet, this is for forwards-compatibility.
7475
*/
7576
export class FineTunesPage extends Page<FineTune> {}
77+
// alias so we can export it in the namespace
78+
type _FineTunesPage = FineTunesPage;
7679

7780
export interface FineTune {
7881
id: string;
@@ -81,7 +84,7 @@ export interface FineTune {
8184

8285
fine_tuned_model: string | null;
8386

84-
hyperparams: unknown;
87+
hyperparams: FineTune.Hyperparams;
8588

8689
model: string;
8790

@@ -102,6 +105,24 @@ export interface FineTune {
102105
events?: Array<FineTuneEvent>;
103106
}
104107

108+
export namespace FineTune {
109+
export interface Hyperparams {
110+
batch_size: number;
111+
112+
learning_rate_multiplier: number;
113+
114+
n_epochs: number;
115+
116+
prompt_loss_weight: number;
117+
118+
classification_n_classes?: number;
119+
120+
classification_positive_class?: string;
121+
122+
compute_classification_metrics?: boolean;
123+
}
124+
}
125+
105126
export interface FineTuneEvent {
106127
created_at: number;
107128

@@ -281,7 +302,7 @@ export namespace FineTunes {
281302
export import FineTune = API.FineTune;
282303
export import FineTuneEvent = API.FineTuneEvent;
283304
export import FineTuneEventsListResponse = API.FineTuneEventsListResponse;
284-
export import FineTunesPage = API.FineTunesPage;
305+
export type FineTunesPage = _FineTunesPage;
285306
export import FineTuneCreateParams = API.FineTuneCreateParams;
286307
export import FineTuneListEventsParams = API.FineTuneListEventsParams;
287308
}

‎src/resources/models.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export class Models extends APIResource {
3434
* Note: no pagination actually occurs yet, this is for forwards-compatibility.
3535
*/
3636
export class ModelsPage extends Page<Model> {}
37+
// alias so we can export it in the namespace
38+
type _ModelsPage = ModelsPage;
3739

3840
export interface Model {
3941
id: string;
@@ -56,5 +58,5 @@ export interface ModelDeleted {
5658
export namespace Models {
5759
export import Model = API.Model;
5860
export import ModelDeleted = API.ModelDeleted;
59-
export import ModelsPage = API.ModelsPage;
61+
export type ModelsPage = _ModelsPage;
6062
}

‎src/streaming.ts

+136-74
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,19 @@
11
import type { Response } from 'openai/_shims/fetch';
2+
23
import { APIResponse, Headers, createResponseHeaders } from './core';
34

5+
type Bytes = string | ArrayBuffer | Uint8Array | Buffer | null | undefined;
6+
47
type ServerSentEvent = {
58
event: string | null;
69
data: string;
710
raw: string[];
811
};
912

10-
class SSEDecoder {
11-
private data: string[];
12-
private event: string | null;
13-
private chunks: string[];
14-
15-
constructor() {
16-
this.event = null;
17-
this.data = [];
18-
this.chunks = [];
19-
}
20-
21-
decode(line: string) {
22-
if (line.endsWith('\r')) {
23-
line = line.substring(0, line.length - 1);
24-
}
25-
26-
if (!line) {
27-
// empty line and we didn't previously encounter any messages
28-
if (!this.event && !this.data.length) return null;
29-
30-
const sse: ServerSentEvent = {
31-
event: this.event,
32-
data: this.data.join('\n'),
33-
raw: this.chunks,
34-
};
35-
36-
this.event = null;
37-
this.data = [];
38-
this.chunks = [];
39-
40-
return sse;
41-
}
42-
43-
this.chunks.push(line);
44-
45-
if (line.startsWith(':')) {
46-
return null;
47-
}
48-
49-
let [fieldname, _, value] = partition(line, ':');
50-
51-
if (value.startsWith(' ')) {
52-
value = value.substring(1);
53-
}
54-
55-
if (fieldname === 'event') {
56-
this.event = value;
57-
} else if (fieldname === 'data') {
58-
this.data.push(value);
59-
}
60-
61-
return null;
62-
}
63-
}
64-
6513
export class Stream<Item> implements AsyncIterable<Item>, APIResponse<Stream<Item>> {
14+
/** @deprecated - please use the async iterator instead. We plan to add additional helper methods shortly. */
6615
response: Response;
16+
/** @deprecated - we plan to add a different way to access raw response information shortly. */
6717
responseHeaders: Headers;
6818
controller: AbortController;
6919

@@ -81,21 +31,11 @@ export class Stream<Item> implements AsyncIterable<Item>, APIResponse<Stream<Ite
8131
this.controller.abort();
8232
throw new Error(`Attempted to iterate over a response with no body`);
8333
}
84-
8534
const lineDecoder = new LineDecoder();
8635

87-
// @ts-ignore
88-
for await (const chunk of this.response.body) {
89-
let text;
90-
if (chunk instanceof Buffer) {
91-
text = chunk.toString();
92-
} else if ((chunk as any) instanceof Uint8Array) {
93-
text = Buffer.from(chunk).toString();
94-
} else {
95-
text = chunk;
96-
}
97-
98-
for (const line of lineDecoder.decode(text)) {
36+
const iter = readableStreamAsyncIterable<Bytes>(this.response.body);
37+
for await (const chunk of iter) {
38+
for (const line of lineDecoder.decode(chunk)) {
9939
const sse = this.decoder.decode(line);
10040
if (sse) yield sse;
10141
}
@@ -135,7 +75,60 @@ export class Stream<Item> implements AsyncIterable<Item>, APIResponse<Stream<Ite
13575
}
13676
}
13777

138-
const NEWLINE_CHARS = '\n\r\x0b\x0c\x1c\x1d\x1e\x85\u2028\u2029';
78+
class SSEDecoder {
79+
private data: string[];
80+
private event: string | null;
81+
private chunks: string[];
82+
83+
constructor() {
84+
this.event = null;
85+
this.data = [];
86+
this.chunks = [];
87+
}
88+
89+
decode(line: string) {
90+
if (line.endsWith('\r')) {
91+
line = line.substring(0, line.length - 1);
92+
}
93+
94+
if (!line) {
95+
// empty line and we didn't previously encounter any messages
96+
if (!this.event && !this.data.length) return null;
97+
98+
const sse: ServerSentEvent = {
99+
event: this.event,
100+
data: this.data.join('\n'),
101+
raw: this.chunks,
102+
};
103+
104+
this.event = null;
105+
this.data = [];
106+
this.chunks = [];
107+
108+
return sse;
109+
}
110+
111+
this.chunks.push(line);
112+
113+
if (line.startsWith(':')) {
114+
return null;
115+
}
116+
117+
let [fieldname, _, value] = partition(line, ':');
118+
119+
if (value.startsWith(' ')) {
120+
value = value.substring(1);
121+
}
122+
123+
if (fieldname === 'event') {
124+
this.event = value;
125+
} else if (fieldname === 'data') {
126+
this.data.push(value);
127+
}
128+
129+
return null;
130+
}
131+
}
139132

140133
/**
141134
* A re-implementation of httpx's `LineDecoder` in Python that handles incrementally
@@ -144,15 +137,22 @@ const NEWLINE_CHARS = '\n\r\x0b\x0c\x1c\x1d\x1e\x85\u2028\u2029';
144137
* https://github.com/encode/httpx/blob/920333ea98118e9cf617f246905d7b202510941c/httpx/_decoders.py#L258
145138
*/
146139
class LineDecoder {
140+
// prettier-ignore
141+
static NEWLINE_CHARS = new Set(['\n', '\r', '\x0b', '\x0c', '\x1c', '\x1d', '\x1e', '\x85', '\u2028', '\u2029']);
142+
static NEWLINE_REGEXP = /\r\n|[\n\r\x0b\x0c\x1c\x1d\x1e\x85\u2028\u2029]/g;
143+
147144
buffer: string[];
148145
trailingCR: boolean;
146+
textDecoder: any; // TextDecoder found in browsers; not typed to avoid pulling in either "dom" or "node" types.
149147

150148
constructor() {
151149
this.buffer = [];
152150
this.trailingCR = false;
153151
}
154152

155-
decode(text: string): string[] {
153+
decode(chunk: Bytes): string[] {
154+
let text = this.decodeText(chunk);
155+
156156
if (this.trailingCR) {
157157
text = '\r' + text;
158158
this.trailingCR = false;
@@ -166,10 +166,10 @@ class LineDecoder {
166166
return [];
167167
}
168168

169-
const trailing_newline = NEWLINE_CHARS.includes(text.slice(-1));
170-
let lines = text.split(/\r\n|[\n\r\x0b\x0c\x1c\x1d\x1e\x85\u2028\u2029]/g);
169+
const trailingNewline = LineDecoder.NEWLINE_CHARS.has(text[text.length - 1] || '');
170+
let lines = text.split(LineDecoder.NEWLINE_REGEXP);
171171

172-
if (lines.length === 1 && !trailing_newline) {
172+
if (lines.length === 1 && !trailingNewline) {
173173
this.buffer.push(lines[0]!);
174174
return [];
175175
}
@@ -179,13 +179,50 @@ class LineDecoder {
179179
this.buffer = [];
180180
}
181181

182-
if (!trailing_newline) {
182+
if (!trailingNewline) {
183183
this.buffer = [lines.pop() || ''];
184184
}
185185

186186
return lines;
187187
}
188188

189+
decodeText(bytes: Bytes): string {
190+
if (bytes == null) return '';
191+
if (typeof bytes === 'string') return bytes;
192+
193+
// Node:
194+
if (typeof Buffer !== 'undefined') {
195+
if (bytes instanceof Buffer) {
196+
return bytes.toString();
197+
}
198+
if (bytes instanceof Uint8Array) {
199+
return Buffer.from(bytes).toString();
200+
}
201+
202+
throw new Error(
203+
`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.`,
204+
);
205+
}
206+
207+
// Browser
208+
if (typeof TextDecoder !== 'undefined') {
209+
if (bytes instanceof Uint8Array || bytes instanceof ArrayBuffer) {
210+
this.textDecoder ??= new TextDecoder('utf8');
211+
return this.textDecoder.decode(bytes);
212+
}
213+
214+
throw new Error(
215+
`Unexpected: received non-Uint8Array/ArrayBuffer (${
216+
(bytes as any).constructor.name
217+
}) in a web platform. Please report this error.`,
218+
);
219+
}
220+
221+
throw new Error(
222+
`Unexpected: neither Buffer nor TextDecoder are available as globals. Please report this error.`,
223+
);
224+
}
225+
189226
flush(): string[] {
190227
if (!this.buffer.length && !this.trailingCR) {
191228
return [];
@@ -206,3 +243,28 @@ function partition(str: string, delimiter: string): [string, string, string] {
206243

207244
return [str, '', ''];
208245
}
246+
247+
/**
248+
* Most browsers don't yet have async iterable support for ReadableStream,
249+
* and Node has a very different way of reading bytes from its "ReadableStream".
250+
*
251+
* This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1624185965
252+
*/
253+
function readableStreamAsyncIterable<T>(stream: any): AsyncIterableIterator<T> {
254+
if (stream[Symbol.asyncIterator]) return stream[Symbol.asyncIterator];
255+
256+
const reader = stream.getReader();
257+
return {
258+
next() {
259+
return reader.read();
260+
},
261+
async return() {
262+
reader.cancel();
263+
reader.releaseLock();
264+
return { done: true, value: undefined };
265+
},
266+
[Symbol.asyncIterator]() {
267+
return this;
268+
},
269+
};
270+
}

‎src/uploads.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export type ToFileInput = Uploadable | Exclude<BlobPart, string> | AsyncIterable
9696
* @returns a {@link File} with the given properties
9797
*/
9898
export async function toFile(
99-
value: ToFileInput,
99+
value: ToFileInput | PromiseLike<ToFileInput>,
100100
name?: string | null | undefined,
101101
options: FilePropertyBag | undefined = {},
102102
): Promise<FileLike> {
@@ -121,8 +121,9 @@ export async function toFile(
121121
return new File(bits, name, options);
122122
}
123123

124-
async function getBytes(value: ToFileInput): Promise<Array<BlobPart>> {
125-
if (value instanceof Promise) return getBytes(await (value as any));
124+
async function getBytes(value: ToFileInput | PromiseLike<ToFileInput>): Promise<Array<BlobPart>> {
125+
// resolve input promise or promiselike object
126+
value = await value;
126127

127128
let parts: Array<BlobPart> = [];
128129
if (

‎src/version.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const VERSION = '4.0.0-beta.2';
1+
export const VERSION = '4.0.0-beta.3';

‎tests/api-resources/chat/completions.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('resource completions', () => {
2626
frequency_penalty: -2,
2727
function_call: 'none',
2828
functions: [{ name: 'string', description: 'string', parameters: { foo: 'bar' } }],
29-
logit_bias: {},
29+
logit_bias: { foo: 0 },
3030
max_tokens: 0,
3131
n: 1,
3232
presence_penalty: -2,

‎tests/api-resources/completions.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('resource completions', () => {
1616
best_of: 0,
1717
echo: true,
1818
frequency_penalty: -2,
19-
logit_bias: {},
19+
logit_bias: { foo: 0 },
2020
logprobs: 0,
2121
max_tokens: 16,
2222
n: 1,

‎tests/index.test.ts

+57-4
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,64 @@ describe('instantiate client', () => {
1717
process.env = env;
1818
});
1919

20-
test('defaultHeaders are passed through', () => {
21-
const client = new OpenAI({ defaultHeaders: { 'X-My-Default-Header': '2' }, apiKey: 'my api key' });
20+
describe('defaultHeaders', () => {
21+
const client = new OpenAI({
22+
baseURL: 'http://localhost:5000/',
23+
defaultHeaders: { 'X-My-Default-Header': '2' },
24+
apiKey: 'my api key',
25+
});
26+
27+
test('they are used in the request', () => {
28+
const { req } = client.buildRequest({ path: '/foo', method: 'post' });
29+
expect((req.headers as Headers)['X-My-Default-Header']).toEqual('2');
30+
});
2231

23-
const { req } = client.buildRequest({ path: '/foo', method: 'post' });
24-
expect((req.headers as Headers)['X-My-Default-Header']).toEqual('2');
32+
test('can be overriden with `undefined`', () => {
33+
const { req } = client.buildRequest({
34+
path: '/foo',
35+
method: 'post',
36+
headers: { 'X-My-Default-Header': undefined },
37+
});
38+
expect((req.headers as Headers)['X-My-Default-Header']).toBeUndefined();
39+
});
40+
41+
test('can be overriden with `null`', () => {
42+
const { req } = client.buildRequest({
43+
path: '/foo',
44+
method: 'post',
45+
headers: { 'X-My-Default-Header': null },
46+
});
47+
expect((req.headers as Headers)['X-My-Default-Header']).toBeUndefined();
48+
});
49+
});
50+
51+
describe('defaultQuery', () => {
52+
test('with null query params given', () => {
53+
const client = new OpenAI({
54+
baseURL: 'http://localhost:5000/',
55+
defaultQuery: { apiVersion: 'foo' },
56+
apiKey: 'my api key',
57+
});
58+
expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo');
59+
});
60+
61+
test('multiple default query params', () => {
62+
const client = new OpenAI({
63+
baseURL: 'http://localhost:5000/',
64+
defaultQuery: { apiVersion: 'foo', hello: 'world' },
65+
apiKey: 'my api key',
66+
});
67+
expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo&hello=world');
68+
});
69+
70+
test('overriding with `undefined`', () => {
71+
const client = new OpenAI({
72+
baseURL: 'http://localhost:5000/',
73+
defaultQuery: { hello: 'world' },
74+
apiKey: 'my api key',
75+
});
76+
expect(client.buildURL('/foo', { hello: undefined })).toEqual('http://localhost:5000/foo');
77+
});
2578
});
2679

2780
describe('baseUrl', () => {

‎tsconfig.build.json

+15-26
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,32 @@
11
{
2+
"extends": "./tsconfig.json",
23
"include": ["dist/src"],
34
"exclude": [],
45
"compilerOptions": {
5-
"target": "es2019",
6-
"lib": ["es2020"],
7-
"module": "commonjs",
8-
"moduleResolution": "node",
9-
"esModuleInterop": true,
106
"rootDir": "./dist/src",
11-
"baseUrl": "./",
127
"paths": {
138
"openai/_shims/*": ["dist/src/_shims/*.node"],
149
"openai": ["dist/src/index.ts"],
1510
"openai/*": ["dist/src/*"],
1611
"digest-fetch": ["./typings/digest-fetch"]
1712
},
18-
13+
"noEmit": false,
1914
"declaration": true,
2015
"declarationMap": true,
2116
"outDir": "dist",
2217
"pretty": true,
23-
"sourceMap": true,
24-
"resolveJsonModule": true,
25-
26-
"forceConsistentCasingInFileNames": true,
27-
28-
"strict": true,
29-
"noImplicitAny": true,
30-
"strictNullChecks": true,
31-
"strictFunctionTypes": true,
32-
"strictBindCallApply": true,
33-
"strictPropertyInitialization": true,
34-
"noImplicitThis": true,
35-
"alwaysStrict": true,
36-
"exactOptionalPropertyTypes": true,
37-
"noUncheckedIndexedAccess": true,
38-
"noImplicitOverride": true,
39-
"noPropertyAccessFromIndexSignature": true,
40-
41-
"skipLibCheck": true
18+
"sourceMap": true
19+
},
20+
"tsc-alias": {
21+
"resolveFullPaths": true,
22+
"fileExtensions": {
23+
"inputGlob": "{mjs,cjs,js,jsx,mts,cts,ts,tsx}"
24+
},
25+
"replacers": {
26+
"replace-absolute-imports": {
27+
"enabled": true,
28+
"file": "./scripts/replace-self-referencing-imports.js"
29+
}
30+
}
4231
}
4332
}

‎tsconfig.json

+1-6
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,15 @@
66
"module": "commonjs",
77
"moduleResolution": "node",
88
"esModuleInterop": true,
9-
"rootDir": "./src",
109
"baseUrl": "./",
1110
"paths": {
1211
"openai/_shims/*": ["src/_shims/*.node"],
1312
"openai": ["src/index.ts"],
1413
"openai/*": ["src/*"],
1514
"digest-fetch": ["./typings/digest-fetch"]
1615
},
16+
"noEmit": true,
1717

18-
"declaration": true,
19-
"declarationMap": true,
20-
"outDir": "dist",
21-
"pretty": true,
22-
"sourceMap": true,
2318
"resolveJsonModule": true,
2419

2520
"forceConsistentCasingInFileNames": true,

0 commit comments

Comments
 (0)
Please sign in to comment.