Skip to content

Commit

Permalink
feat(client): support importing node or web shims manually (#325)
Browse files Browse the repository at this point in the history
  • Loading branch information
stainless-bot authored Sep 20, 2023
1 parent 0386937 commit 628f293
Show file tree
Hide file tree
Showing 147 changed files with 12,522 additions and 1,325 deletions.
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ CHANGELOG.md
/ecosystem-tests
/node_modules
/deno

# don't format tsc output, will break source maps
/dist
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,5 +318,9 @@ The following runtimes are supported:
- Bun 1.0 or later.
- Cloudflare Workers.
- Vercel Edge Runtime.
- Jest 28 or greater with the `"node"` environment (`"jsdom"` is not supported at this time).
- Nitro v2.6 or greater.

Note that React Native is not supported at this time.

If you are interested in other runtime environments, please open or upvote an issue on GitHub.
15 changes: 4 additions & 11 deletions build
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ rm -rf dist; mkdir dist
# Copy src to dist/src and build from dist/src into dist, so that
# the source map for index.js.map will refer to ./src/index.ts etc
cp -rp src README.md dist
rm dist/src/_shims/*-deno.*
rm dist/src/_shims/*-deno.ts dist/src/_shims/auto/*-deno.ts
for file in LICENSE CHANGELOG.md; do
if [ -e "${file}" ]; then cp "${file}" dist; fi
done
Expand All @@ -27,8 +27,8 @@ node scripts/make-dist-package-json.cjs > dist/package.json
# build to .js/.mjs/.d.ts files
npm exec tsc-multi
# copy over handwritten .js/.mjs/.d.ts files
cp src/_shims/*.{d.ts,js,mjs} dist/_shims
npm exec tsc-alias -- -p tsconfig.build.json
cp src/_shims/*.{d.ts,js,mjs,md} dist/_shims
cp src/_shims/auto/*.{d.ts,js,mjs} dist/_shims/auto
# we need to add exports = module.exports = OpenAI Node to index.js;
# No way to get that from index.ts because it would cause compile errors
# when building .mjs
Expand All @@ -40,14 +40,7 @@ node scripts/fix-index-exports.cjs
cp dist/index.d.ts dist/index.d.mts
cp tsconfig.dist-src.json dist/src/tsconfig.json

# strip out lib="dom" and types="node" references; these are needed at build time,
# but would pollute the user's TS environment
find dist -type f -exec node scripts/remove-triple-slash-references.js {} +
# strip out `unknown extends RequestInit ? never :` from dist/src/_shims;
# these cause problems when viewing the .ts source files in go to definition
find dist/src/_shims -type f -exec node scripts/replace-shim-guards.js {} +

npm exec prettier -- --loglevel=warn --write .
node scripts/postprocess-files.cjs

# make sure that nothing crashes when we require the output CJS or
# import the output ESM
Expand Down
34 changes: 34 additions & 0 deletions ecosystem-tests/bun/openai.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import OpenAI, { toFile } from 'openai';
import fs from 'fs';
import { distance } from 'fastest-levenshtein';
import { test, expect } from 'bun:test';
import { ChatCompletion } from 'openai/resources/chat/completions';

const url = 'https://audio-samples.github.io/samples/mp3/blizzard_biased/sample-1.mp3';
const filename = 'sample-1.mp3';
Expand Down Expand Up @@ -42,6 +43,39 @@ test(`basic request works`, async function () {
expectSimilar(completion.choices[0]?.message?.content, 'This is a test', 10);
});

test(`raw response`, async function () {
const response = await client.chat.completions
.create({
model: 'gpt-4',
messages: [{ role: 'user', content: 'Say this is a test' }],
})
.asResponse();

// test that we can use web Response API
const { body } = response;
if (!body) throw new Error('expected response.body to be defined');

const reader = body.getReader();
const chunks: Uint8Array[] = [];
let result;
do {
result = await reader.read();
if (!result.done) chunks.push(result.value);
} while (!result.done);

reader.releaseLock();

let offset = 0;
const merged = new Uint8Array(chunks.reduce((total, chunk) => total + chunk.length, 0));
for (const chunk of chunks) {
merged.set(chunk, offset);
offset += chunk.length;
}

const json: ChatCompletion = JSON.parse(new TextDecoder().decode(merged));
expectSimilar(json.choices[0]?.message.content || '', 'This is a test', 10);
});

test(`streaming works`, async function () {
const stream = await client.chat.completions.create({
model: 'gpt-4',
Expand Down
3 changes: 2 additions & 1 deletion ecosystem-tests/bun/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"include": ["*.ts"],
"compilerOptions": {
"lib": ["ESNext"],
"module": "esnext",
Expand All @@ -8,7 +9,7 @@
"allowImportingTsExtensions": true,
"strict": true,
"downlevelIteration": true,
"skipLibCheck": true,
"skipLibCheck": false,
"jsx": "preserve",
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
Expand Down
78 changes: 35 additions & 43 deletions ecosystem-tests/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,20 @@ import path from 'path';
const TAR_NAME = 'openai.tgz';
const PACK_FILE = `.pack/${TAR_NAME}`;

async function defaultNodeRunner() {
await installPackage();
await run('npm', ['run', 'tsc']);
if (state.live) await run('npm', ['test']);
}

const projects = {
'node-ts-cjs': defaultNodeRunner,
'node-ts-cjs-web': defaultNodeRunner,
'node-ts-cjs-auto': defaultNodeRunner,
'node-ts4.5-jest27': defaultNodeRunner,
'node-ts-esm': defaultNodeRunner,
'node-ts-esm-web': defaultNodeRunner,
'node-ts-esm-auto': defaultNodeRunner,
'ts-browser-webpack': async () => {
await installPackage();

Expand Down Expand Up @@ -45,36 +58,6 @@ const projects = {
await run('npm', ['run', 'deploy']);
}
},
'node-ts-cjs': async () => {
await installPackage();

await run('npm', ['run', 'tsc']);

if (state.live) {
await run('npm', ['test']);
}
},
'node-ts-cjs-ts4.5': async () => {
await installPackage();
await run('npm', ['run', 'tsc']);
},
'node-ts-cjs-dom': async () => {
await installPackage();
await run('npm', ['run', 'tsc']);
},
'node-ts-esm': async () => {
await installPackage();

await run('npm', ['run', 'tsc']);

if (state.live) {
await run('npm', ['run', 'test']);
}
},
'node-ts-esm-dom': async () => {
await installPackage();
await run('npm', ['run', 'tsc']);
},
bun: async () => {
if (state.fromNpm) {
await run('bun', ['install', '-D', state.fromNpm]);
Expand Down Expand Up @@ -116,6 +99,7 @@ const projects = {
};

const projectNames = Object.keys(projects) as Array<keyof typeof projects>;
const projectNamesSet = new Set(projectNames);

function parseArgs() {
return yargs(process.argv.slice(2))
Expand Down Expand Up @@ -189,10 +173,13 @@ async function main() {
await buildPackage();
}

const positionalArgs = args._.filter(Boolean);

// For some reason `yargs` doesn't pick up the positional args correctly
const projectsToRun = (
args.projects?.length ? args.projects
: args._.length ? args._
: positionalArgs.length ?
positionalArgs.filter((n) => typeof n === 'string' && (projectNamesSet as Set<string>).has(n))
: projectNames) as typeof projectNames;
console.error(`running projects: ${projectsToRun}`);

Expand Down Expand Up @@ -234,10 +221,11 @@ async function main() {
const project = queue.shift();
if (!project) break;

let stdout, stderr;
// preserve interleaved ordering of writes to stdout/stderr
const chunks: { dest: 'stdout' | 'stderr'; data: string | Buffer }[] = [];
try {
runningProjects.add(project);
const result = await execa(
const child = execa(
'yarn',
[
'tsn',
Expand All @@ -252,16 +240,19 @@ async function main() {
],
{ stdio: 'pipe', encoding: 'utf8', maxBuffer: 100 * 1024 * 1024 },
);
({ stdout, stderr } = result);
child.stdout?.on('data', (data) => chunks.push({ dest: 'stdout', data }));
child.stderr?.on('data', (data) => chunks.push({ dest: 'stderr', data }));
await child;
} catch (error) {
({ stdout, stderr } = error as any);
failed.push(project);
} finally {
runningProjects.delete(project);
}

if (stdout) process.stdout.write(stdout);
if (stderr) process.stderr.write(stderr);
for (const { dest, data } of chunks) {
if (dest === 'stdout') process.stdout.write(data);
else process.stderr.write(data);
}
}
}),
);
Expand All @@ -274,18 +265,21 @@ async function main() {

await withChdir(path.join(rootDir, 'ecosystem-tests', project), async () => {
console.error('\n');
console.error(banner(project));
console.error(banner(`▶️ ${project}`));
console.error('\n');

try {
await withRetry(fn, project, state.retry);
console.error(`✅ - Successfully ran ${project}`);
console.error('\n');
console.error(banner(`✅ ${project}`));
} catch (err) {
if (err && (err as any).shortMessage) {
console.error('❌', (err as any).shortMessage);
console.error((err as any).shortMessage);
} else {
console.error('❌', err);
console.error(err);
}
console.error('\n');
console.error(banner(`❌ ${project}`));
failed.push(project);
}
console.error('\n');
Expand Down Expand Up @@ -331,8 +325,6 @@ async function buildPackage() {
return;
}

await run('yarn', ['build']);

if (!(await pathExists('.pack'))) {
await fs.mkdir('.pack');
}
Expand Down
2 changes: 1 addition & 1 deletion ecosystem-tests/cloudflare-worker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"deploy": "wrangler publish",
"start": "wrangler dev",
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
"test:ci": "WAIT_ON_INTERVAL=10000 start-server-and-test start http://localhost:8787 test"
"test:ci": "start-server-and-test start http://localhost:8787 test"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20230419.0",
Expand Down
34 changes: 34 additions & 0 deletions ecosystem-tests/cloudflare-worker/src/uploadWebApiTestCases.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import OpenAI, { toFile } from 'openai';
import { TranscriptionCreateParams } from 'openai/resources/audio/transcriptions';
import { ChatCompletion } from 'openai/resources/chat/completions';

/**
* Tests uploads using various Web API data objects.
Expand Down Expand Up @@ -45,6 +46,39 @@ export function uploadWebApiTestCases({
await client.audio.transcriptions.create({ file: 'test', model: 'whisper-1' });
}

it(`raw response`, async function () {
const response = await client.chat.completions
.create({
model: 'gpt-4',
messages: [{ role: 'user', content: 'Say this is a test' }],
})
.asResponse();

// test that we can use web Response API
const { body } = response;
if (!body) throw new Error('expected response.body to be defined');

const reader = body.getReader();
const chunks: Uint8Array[] = [];
let result;
do {
result = await reader.read();
if (!result.done) chunks.push(result.value);
} while (!result.done);

reader.releaseLock();

let offset = 0;
const merged = new Uint8Array(chunks.reduce((total, chunk) => total + chunk.length, 0));
for (const chunk of chunks) {
merged.set(chunk, offset);
offset += chunk.length;
}

const json: ChatCompletion = JSON.parse(new TextDecoder().decode(merged));
expectSimilar(json.choices[0]?.message.content || '', 'This is a test', 10);
});

it(`streaming works`, async function () {
const stream = await client.chat.completions.create({
model: 'gpt-4',
Expand Down
5 changes: 5 additions & 0 deletions ecosystem-tests/cloudflare-worker/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ type Test = { description: string; handler: () => Promise<void> };

export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
// start-server-and-test polls / to see if the server is up and running
if (url.pathname === '/') return new Response();
// then the test code requests /test
if (url.pathname !== '/test') return new Response(null, { status: 404 });
try {
console.error('importing openai');
const { default: OpenAI } = await import('openai');
Expand Down
2 changes: 1 addition & 1 deletion ecosystem-tests/cloudflare-worker/tests/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fetch from 'node-fetch';
it(
'works',
async () => {
expect(await (await fetch('http://localhost:8787')).text()).toEqual('Passed!');
expect(await (await fetch('http://localhost:8787/test')).text()).toEqual('Passed!');
},
3 * 60000
);
1 change: 1 addition & 0 deletions ecosystem-tests/cloudflare-worker/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"include": ["src/*.ts"],
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */

Expand Down
Loading

0 comments on commit 628f293

Please sign in to comment.