Skip to content

Commit 96e5b7f

Browse files
committed
v4.0.0-beta.6
1 parent 07b3504 commit 96e5b7f

File tree

13 files changed

+268
-125
lines changed

13 files changed

+268
-125
lines changed

ecosystem-tests/cli.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ const projects = {
1313

1414
await run('npm', ['run', 'tsc']);
1515
await run('npm', ['run', 'build']);
16+
17+
if (state.live) {
18+
await run('npm', ['run', 'test:ci']);
19+
}
1620
},
1721
'vercel-edge': async () => {
1822
await installPackage();

ecosystem-tests/ts-browser-webpack/package.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,22 @@
55
"description": "ts-browser-webpack",
66
"scripts": {
77
"tsc": "tsc",
8-
"test": "echo \"Error: no test specified\" && exit 1",
9-
"serve": "webpack-cli serve --mode development",
10-
"build": "webpack"
8+
"serve": "webpack-cli serve",
9+
"build": "webpack",
10+
"test": "ts-node src/test.ts",
11+
"test:ci": "WAIT_ON_INTERVAL=10000 start-server-and-test serve http://localhost:8080 test"
1112
},
1213
"devDependencies": {
1314
"babel-core": "^6.26.3",
1415
"babel-loader": "^9.1.2",
1516
"babel-preset-es2015": "^6.24.1",
17+
"fastest-levenshtein": "^1.0.16",
1618
"force": "^0.0.3",
1719
"html-webpack-plugin": "^5.5.3",
20+
"puppeteer": "^20.8.3",
21+
"start-server-and-test": "^2.0.0",
1822
"ts-loader": "^9.4.3",
23+
"ts-node": "^10.9.1",
1924
"typescript": "^5.0.4",
2025
"webpack": "^5.87.0",
2126
"webpack-cli": "^5.0.2",

ecosystem-tests/ts-browser-webpack/public/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
<title>Package in the Browser</title>
66
</head>
77
<body>
8-
<button id="myButton">Say Hello</button>
8+
<div id="running">Running tests...</div>
99
</body>
1010
</html>
Lines changed: 150 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,164 @@
1-
import OpenAI from 'openai';
2-
import { TranscriptionCreateParams } from 'openai/resources/audio';
1+
import OpenAI, { toFile } from 'openai';
2+
import { distance } from 'fastest-levenshtein';
33

4-
// smoke test that importing and constructing the client works
5-
const openai = new OpenAI({ apiKey: '<invalid API key>' });
6-
console.log(openai);
4+
type TestCase = {
5+
path: string[];
6+
run: () => any;
7+
timeout?: number;
8+
};
9+
10+
const tests: TestCase[] = [];
11+
12+
type TestResult = { path: string[]; passed: boolean; error?: string };
13+
14+
async function runTests() {
15+
const results: TestResult[] = [];
16+
function displayResults() {
17+
let pre = document.getElementById('results');
18+
if (!pre) {
19+
pre = document.createElement('pre');
20+
pre.id = 'results';
21+
document.body.appendChild(pre);
22+
}
23+
pre.innerText = JSON.stringify(results, null, 2);
24+
}
25+
for (const { path, run, timeout } of tests) {
26+
console.log('running', ...path);
27+
try {
28+
await Promise.race([
29+
run(),
30+
new Promise((_, reject) =>
31+
setTimeout(() => reject(new Error(`Test timed out after ${timeout} ms`)), timeout),
32+
),
33+
]);
34+
console.log('passed ', ...path);
35+
results.push({ path, passed: true });
36+
} catch (error) {
37+
console.log('error ', ...path);
38+
console.error(error);
39+
results.push({ path, passed: false, error: error instanceof Error ? error.stack : String(error) });
40+
}
41+
displayResults();
42+
}
43+
const runningEl = document.getElementById('running');
44+
if (runningEl) runningEl.remove();
45+
}
46+
47+
const testPath: string[] = [];
48+
49+
function describe(description: string, handler: () => void) {
50+
testPath.push(description);
51+
try {
52+
handler();
53+
} finally {
54+
testPath.pop();
55+
}
56+
}
57+
58+
function it(description: string, run: () => any, timeout = 15000) {
59+
tests.push({ path: [...testPath, description], run, timeout });
60+
}
61+
62+
function expect(received: any) {
63+
return {
64+
toEqual(expected: any): void {
65+
if (!Object.is(received, expected)) {
66+
throw new Error(
67+
[`Received: ${JSON.stringify(received)}`, `Expected: ${JSON.stringify(expected)}`].join('\n'),
68+
);
69+
}
70+
},
71+
toBeSimilarTo(comparedTo: string, expectedDistance: number) {
72+
const actualDistance = distance(received, comparedTo);
73+
if (actualDistance < expectedDistance) return;
774

8-
// smoke test that making an API request works, even if it errors
9-
openai.completions
10-
.create({
11-
prompt: 'Say this is a test',
12-
model: 'text-davinci-003',
13-
})
14-
.catch((err) => console.error(err));
75+
throw new Error(
76+
[
77+
`Received: ${JSON.stringify(received)}`,
78+
`Expected: ${JSON.stringify(comparedTo)}`,
79+
`Expected distance: ${expectedDistance}`,
80+
`Received distance: ${actualDistance}`,
81+
].join('\n'),
82+
);
83+
},
84+
};
85+
}
86+
87+
const url = 'https://audio-samples.github.io/samples/mp3/blizzard_biased/sample-1.mp3';
88+
const filename = 'sample-1.mp3';
89+
90+
const correctAnswer =
91+
'It was anxious to find him no one that expectation of a man who were giving his father enjoyment. But he was avoided in sight in the minister to which indeed,';
92+
const model = 'whisper-1';
93+
94+
const params = new URLSearchParams(location.search);
95+
96+
const client = new OpenAI({ apiKey: params.get('apiKey') ?? undefined });
1597

1698
async function typeTests() {
1799
// @ts-expect-error this should error if the `Uploadable` type was resolved correctly
18-
await openai.audio.transcriptions.create({ file: { foo: true }, model: 'whisper-1' });
100+
await client.audio.transcriptions.create({ file: { foo: true }, model: 'whisper-1' });
19101
// @ts-expect-error this should error if the `Uploadable` type was resolved correctly
20-
await openai.audio.transcriptions.create({ file: null, model: 'whisper-1' });
102+
await client.audio.transcriptions.create({ file: null, model: 'whisper-1' });
21103
// @ts-expect-error this should error if the `Uploadable` type was resolved correctly
22-
await openai.audio.transcriptions.create({ file: 'test', model: 'whisper-1' });
104+
await client.audio.transcriptions.create({ file: 'test', model: 'whisper-1' });
105+
}
106+
107+
if (typeof File !== 'undefined') {
108+
it('handles builtinFile', async function () {
109+
const file = await fetch(url)
110+
.then((x) => x.arrayBuffer())
111+
.then((x) => new File([x], filename));
23112

24-
const url = 'https://audio-samples.github.io/samples/mp3/blizzard_biased/sample-1.mp3';
25-
const model = 'whisper-1';
113+
const result = await client.audio.transcriptions.create({ file, model });
114+
expect(result.text).toBeSimilarTo(correctAnswer, 12);
115+
});
116+
}
26117

118+
it('handles Response', async function () {
27119
const file = await fetch(url);
28-
const params: TranscriptionCreateParams = { file, model };
29120

30-
await openai.audio.transcriptions.create(params);
31-
}
121+
const result = await client.audio.transcriptions.create({ file, model });
122+
expect(result.text).toBeSimilarTo(correctAnswer, 12);
123+
});
32124

33-
// --------
34-
class Greeter {
35-
greeting: string;
36-
constructor(message: string) {
37-
this.greeting = message;
38-
}
39-
greet(): string {
40-
return `Hello, ${this.greeting}`;
41-
}
42-
}
125+
const fineTune = `{"prompt": "<prompt text>", "completion": "<ideal generated text>"}`;
43126

44-
const greeter = new Greeter('world');
127+
describe('toFile', () => {
128+
if (typeof Blob !== 'undefined') {
129+
it('handles builtin Blob', async function () {
130+
const result = await client.files.create({
131+
file: await toFile(
132+
// @ts-ignore avoid DOM lib for testing purposes
133+
new Blob([new TextEncoder().encode(fineTune)]),
134+
'finetune.jsonl',
135+
),
136+
purpose: 'fine-tune',
137+
});
138+
expect(result.status).toEqual('uploaded');
139+
});
140+
}
141+
it('handles Uint8Array', async function () {
142+
const result = await client.files.create({
143+
file: await toFile(new TextEncoder().encode(fineTune), 'finetune.jsonl'),
144+
purpose: 'fine-tune',
145+
});
146+
expect(result.status).toEqual('uploaded');
147+
});
148+
it('handles ArrayBuffer', async function () {
149+
const result = await client.files.create({
150+
file: await toFile(new TextEncoder().encode(fineTune).buffer, 'finetune.jsonl'),
151+
purpose: 'fine-tune',
152+
});
153+
expect(result.status).toEqual('uploaded');
154+
});
155+
it('handles DataView', async function () {
156+
const result = await client.files.create({
157+
file: await toFile(new DataView(new TextEncoder().encode(fineTune).buffer), 'finetune.jsonl'),
158+
purpose: 'fine-tune',
159+
});
160+
expect(result.status).toEqual('uploaded');
161+
});
162+
});
45163

46-
const button = document.getElementById('myButton')!;
47-
button.onclick = () => {
48-
alert(greeter.greet());
49-
};
164+
runTests();
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import puppeteer from 'puppeteer'
2+
3+
(async () => {
4+
const browser = await puppeteer.launch();
5+
try {
6+
const page = await browser.newPage();
7+
8+
const apiKey = process.env.OPENAI_API_KEY
9+
10+
if (!apiKey) throw new Error('missing process.env.OPENAI_API_KEY')
11+
12+
// Navigate the page to a URL
13+
await page.goto(`http://localhost:8080/index.html?apiKey=${apiKey}`);
14+
15+
await page.waitForSelector('#running', { timeout: 15000 })
16+
17+
let start = Date.now()
18+
while (await page.$('#running') != null && Date.now() - start < 60000) {
19+
await new Promise(r => setTimeout(r, 1000))
20+
}
21+
22+
let results
23+
const resultsEl = await page.$('#results')
24+
if (resultsEl) {
25+
const text = await page.evaluate(el => el.textContent, resultsEl)
26+
results = text ? JSON.parse(text) : undefined
27+
}
28+
29+
if (!Array.isArray(results)) {
30+
throw new Error(`failed to get test results from page`)
31+
}
32+
const failed = results.filter(r => !r.passed)
33+
if (failed.length) {
34+
throw new Error(`${failed.length} of ${results.length} tests failed: ${JSON.stringify(failed, null, 2)}`)
35+
}
36+
console.log(`${results.length} tests passed!`)
37+
} finally {
38+
await browser.close();
39+
}
40+
})();

ecosystem-tests/ts-browser-webpack/webpack.config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const buildPath = path.resolve(__dirname, 'dist');
88
module.exports = {
99
entry: path.join(srcPath, 'index.ts'),
1010

11+
mode: 'development',
12+
1113
output: {
1214
path: buildPath,
1315
filename: 'bundle.js',
@@ -32,7 +34,7 @@ module.exports = {
3234
extensions: ['*', '.js', '.ts'],
3335
},
3436

35-
devtool: 'inline-source-map',
37+
devtool: 'eval',
3638

3739
plugins: [
3840
new HtmlWebpackPlugin({

package.json

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "openai",
3-
"version": "4.0.0-beta.5",
3+
"version": "4.0.0-beta.6",
44
"description": "Client library for the OpenAI API",
55
"author": "OpenAI <[email protected]>",
66
"types": "dist/index.d.ts",
@@ -48,17 +48,17 @@
4848
"types": "./dist/index.d.mts",
4949
"default": "./dist/index.mjs"
5050
},
51-
"./*": {
51+
"./*.mjs": {
5252
"types": "./dist/*.d.ts",
53-
"require": "./dist/*.js",
5453
"default": "./dist/*.mjs"
5554
},
5655
"./*.js": {
5756
"types": "./dist/*.d.ts",
5857
"default": "./dist/*.js"
5958
},
60-
"./*.mjs": {
59+
"./*": {
6160
"types": "./dist/*.d.ts",
61+
"require": "./dist/*.js",
6262
"default": "./dist/*.mjs"
6363
}
6464
},
@@ -74,14 +74,12 @@
7474
"dependencies": {
7575
"@types/node": "^18.11.18",
7676
"@types/node-fetch": "^2.6.4",
77-
"@types/qs": "^6.9.7",
7877
"abort-controller": "^3.0.0",
7978
"agentkeepalive": "^4.2.1",
8079
"digest-fetch": "^1.3.0",
8180
"form-data-encoder": "1.7.2",
8281
"formdata-node": "^4.3.2",
83-
"node-fetch": "^2.6.7",
84-
"qs": "^6.10.3"
82+
"node-fetch": "^2.6.7"
8583
},
8684
"devDependencies": {
8785
"@types/jest": "^29.4.0",

src/_shims/fetch.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ exports.Request = Request;
1010
exports.Response = Response;
1111
exports.Headers = Headers;
1212

13-
exports.isPolyfilled = true;
13+
exports.isPolyfilled = false;

0 commit comments

Comments
 (0)