Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: openai/openai-node
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v4.24.1
Choose a base ref
...
head repository: openai/openai-node
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v4.24.2
Choose a head ref
  • 7 commits
  • 20 files changed
  • 1 contributor

Commits on Jan 9, 2024

  1. Unverified

    This user has not yet uploaded their public signing key.
    Copy the full SHA
    8165725 View commit details
  2. Unverified

    This user has not yet uploaded their public signing key.
    Copy the full SHA
    a952e50 View commit details
  3. Copy the full SHA
    76ef16c View commit details
  4. fix(headers): always send lowercase headers and strip undefined (BREA…

    …KING in rare cases) (#608)
    
    BREAKING: If you previously sent `My-Header: foo` and `my-header: bar`,
    both would get sent. Now, only one will.
    If you previously sent `My-Header: undefined`, it would send as such.
    Now, the header will not be sent.
    stainless-bot committed Jan 9, 2024
    Copy the full SHA
    842efac View commit details
  5. Copy the full SHA
    f5b19fd View commit details
  6. Copy the full SHA
    6c3cf35 View commit details
  7. release: 4.24.2

    stainless-bot committed Jan 9, 2024
    Copy the full SHA
    6fa28ea View commit details
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "4.24.1"
".": "4.24.2"
}
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

## 4.24.2 (2024-01-08)

Full Changelog: [v4.24.1...v4.24.2](https://github.com/openai/openai-node/compare/v4.24.1...v4.24.2)

### Bug Fixes

* **headers:** always send lowercase headers and strip undefined (BREAKING in rare cases) ([#608](https://github.com/openai/openai-node/issues/608)) ([4ea159f](https://github.com/openai/openai-node/commit/4ea159f0aa9a1f4c365c74ee726714fe692ddf9f))


### Chores

* add .keep files for examples and custom code directories ([#612](https://github.com/openai/openai-node/issues/612)) ([5e0f733](https://github.com/openai/openai-node/commit/5e0f733d3cd3c8e6d41659141168cd0708e017a3))
* **internal:** bump license ([#605](https://github.com/openai/openai-node/issues/605)) ([045ee74](https://github.com/openai/openai-node/commit/045ee74fd3ffba9e6d1301fe1ffd8bd3c63720a2))
* **internal:** improve type signatures ([#609](https://github.com/openai/openai-node/issues/609)) ([e1ccc82](https://github.com/openai/openai-node/commit/e1ccc82e4991262a631dcffa4d09bdc553e50fbb))


### Documentation

* fix docstring typos ([#600](https://github.com/openai/openai-node/issues/600)) ([1934fa1](https://github.com/openai/openai-node/commit/1934fa15f654ea89e226457f76febe6015616f6c))
* improve audio example to show how to stream to a file ([#598](https://github.com/openai/openai-node/issues/598)) ([e950ad9](https://github.com/openai/openai-node/commit/e950ad969e845d608ed71bd3e3095cd6c941d93d))

## 4.24.1 (2023-12-22)

Full Changelog: [v4.24.0...v4.24.1](https://github.com/openai/openai-node/compare/v4.24.0...v4.24.1)
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2023 OpenAI
Copyright 2024 OpenAI

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ You can import in Deno via:
<!-- x-release-please-start-version -->

```ts
import OpenAI from 'https://deno.land/x/openai@v4.24.1/mod.ts';
import OpenAI from 'https://deno.land/x/openai@v4.24.2/mod.ts';
```

<!-- x-release-please-end -->
2 changes: 1 addition & 1 deletion build-deno
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ This is a build produced from https://github.com/openai/openai-node – please g
Usage:
\`\`\`ts
import OpenAI from "https://deno.land/x/openai@v4.24.1/mod.ts";
import OpenAI from "https://deno.land/x/openai@v4.24.2/mod.ts";
const client = new OpenAI();
\`\`\`
4 changes: 4 additions & 0 deletions examples/.keep
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
File generated from our OpenAPI spec by Stainless.

This directory can be used to store example files demonstrating usage of this SDK.
It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.
44 changes: 41 additions & 3 deletions examples/audio.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env -S npm run tsn -T
import 'openai/shims/node';

import OpenAI, { toFile } from 'openai';
import fs from 'fs/promises';
import fs from 'fs';
import path from 'path';

// gets API Key from environment variable OPENAI_API_KEY
@@ -10,14 +11,34 @@ const openai = new OpenAI();
const speechFile = path.resolve(__dirname, './speech.mp3');

async function main() {
await streamingDemoNode();
await blockingDemo();
}
main();

async function streamingDemoNode() {
const response = await openai.audio.speech.create({
model: 'tts-1',
voice: 'alloy',
input: 'the quick brown chicken jumped over the lazy dogs',
});

const stream = response.body;

console.log(`Streaming response to ${speechFile}`);
await streamToFile(stream, speechFile);
console.log('Finished streaming');
}

async function blockingDemo() {
const mp3 = await openai.audio.speech.create({
model: 'tts-1',
voice: 'alloy',
input: 'the quick brown fox jumped over the lazy dogs',
});

const buffer = Buffer.from(await mp3.arrayBuffer());
await fs.writeFile(speechFile, buffer);
await fs.promises.writeFile(speechFile, buffer);

const transcription = await openai.audio.transcriptions.create({
file: await toFile(buffer, 'speech.mp3'),
@@ -32,4 +53,21 @@ async function main() {
console.log(translation.text);
}

main();
/**
* Note, this is Node-specific.
*
* Other runtimes would need a different `fs`,
* and would also use a web ReadableStream,
* which is different from a Node ReadableStream.
*/
async function streamToFile(stream: NodeJS.ReadableStream, path: fs.PathLike) {
return new Promise((resolve, reject) => {
const writeStream = fs.createWriteStream(path).on('error', reject).on('finish', resolve);

// If you don't see a `stream.pipe` method and you're using Node you might need to add `import 'openai/shims/node'` at the top of your entrypoint file.
stream.pipe(writeStream).on('error', (error) => {
writeStream.close();
reject(error);
});
});
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "openai",
"version": "4.24.1",
"version": "4.24.2",
"description": "The official TypeScript library for the OpenAI API",
"author": "OpenAI <support@openai.com>",
"types": "dist/index.d.ts",
2 changes: 1 addition & 1 deletion src/_shims/index-deno.ts
Original file line number Diff line number Diff line change
@@ -64,7 +64,7 @@ const _Blob = Blob;
type _Blob = Blob;
export { _Blob as Blob };

export async function getMultipartRequestOptions<T extends {} = Record<string, unknown>>(
export async function getMultipartRequestOptions<T = Record<string, unknown>>(
form: FormData,
opts: RequestOptions<T>,
): Promise<RequestOptions<T>> {
2 changes: 1 addition & 1 deletion src/_shims/index.d.ts
Original file line number Diff line number Diff line change
@@ -65,7 +65,7 @@ export type ReadableStream = SelectType<manual.ReadableStream, auto.ReadableStre
// @ts-ignore
export const ReadableStream: SelectType<typeof manual.ReadableStream, typeof auto.ReadableStream>;

export function getMultipartRequestOptions<T extends {} = Record<string, unknown>>(
export function getMultipartRequestOptions<T = Record<string, unknown>>(
form: FormData,
opts: RequestOptions<T>,
): Promise<RequestOptions<T>>;
2 changes: 1 addition & 1 deletion src/_shims/node-runtime.ts
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ async function fileFromPath(path: string, ...args: any[]): Promise<File> {
const defaultHttpAgent: Agent = new KeepAliveAgent({ keepAlive: true, timeout: 5 * 60 * 1000 });
const defaultHttpsAgent: Agent = new KeepAliveAgent.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1000 });

async function getMultipartRequestOptions<T extends {} = Record<string, unknown>>(
async function getMultipartRequestOptions<T = Record<string, unknown>>(
form: fd.FormData,
opts: RequestOptions<T>,
): Promise<RequestOptions<T>> {
2 changes: 1 addition & 1 deletion src/_shims/registry.ts
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ export interface Shims {
Blob: any;
File: any;
ReadableStream: any;
getMultipartRequestOptions: <T extends {} = Record<string, unknown>>(
getMultipartRequestOptions: <T = Record<string, unknown>>(
form: Shims['FormData'],
opts: RequestOptions<T>,
) => Promise<RequestOptions<T>>;
2 changes: 1 addition & 1 deletion src/_shims/web-runtime.ts
Original file line number Diff line number Diff line change
@@ -84,7 +84,7 @@ export function getRuntime({ manuallyImported }: { manuallyImported?: boolean }
}
}
),
getMultipartRequestOptions: async <T extends {} = Record<string, unknown>>(
getMultipartRequestOptions: async <T = Record<string, unknown>>(
// @ts-ignore
form: FormData,
opts: RequestOptions<T>,
95 changes: 65 additions & 30 deletions src/core.ts
Original file line number Diff line number Diff line change
@@ -217,27 +217,27 @@ export abstract class APIClient {
return `stainless-node-retry-${uuid4()}`;
}

get<Req extends {}, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
get<Req, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
return this.methodRequest('get', path, opts);
}

post<Req extends {}, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
post<Req, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
return this.methodRequest('post', path, opts);
}

patch<Req extends {}, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
patch<Req, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
return this.methodRequest('patch', path, opts);
}

put<Req extends {}, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
put<Req, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
return this.methodRequest('put', path, opts);
}

delete<Req extends {}, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
delete<Req, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
return this.methodRequest('delete', path, opts);
}

private methodRequest<Req extends {}, Rsp>(
private methodRequest<Req, Rsp>(
method: HTTPMethod,
path: string,
opts?: PromiseOrValue<RequestOptions<Req>>,
@@ -269,9 +269,7 @@ export abstract class APIClient {
return null;
}

buildRequest<Req extends {}>(
options: FinalRequestOptions<Req>,
): { req: RequestInit; url: string; timeout: number } {
buildRequest<Req>(options: FinalRequestOptions<Req>): { req: RequestInit; url: string; timeout: number } {
const { method, path, query, headers: headers = {} } = options;

const body =
@@ -301,18 +299,7 @@ export abstract class APIClient {
headers[this.idempotencyHeader] = options.idempotencyKey;
}

const reqHeaders: Record<string, string> = {
...(contentLength && { 'Content-Length': contentLength }),
...this.defaultHeaders(options),
...headers,
};
// let builtin fetch set the Content-Type for multipart bodies
if (isMultipartBody(options.body) && shimsKind !== 'node') {
delete reqHeaders['Content-Type'];
}

// Strip any headers being explicitly omitted with null
Object.keys(reqHeaders).forEach((key) => reqHeaders[key] === null && delete reqHeaders[key]);
const reqHeaders = this.buildHeaders({ options, headers, contentLength });

const req: RequestInit = {
method,
@@ -324,9 +311,35 @@ export abstract class APIClient {
signal: options.signal ?? null,
};

return { req, url, timeout };
}

private buildHeaders({
options,
headers,
contentLength,
}: {
options: FinalRequestOptions;
headers: Record<string, string | null | undefined>;
contentLength: string | null | undefined;
}): Record<string, string> {
const reqHeaders: Record<string, string> = {};
if (contentLength) {
reqHeaders['content-length'] = contentLength;
}

const defaultHeaders = this.defaultHeaders(options);
applyHeadersMut(reqHeaders, defaultHeaders);
applyHeadersMut(reqHeaders, headers);

// let builtin fetch set the Content-Type for multipart bodies
if (isMultipartBody(options.body) && shimsKind !== 'node') {
delete reqHeaders['content-type'];
}

this.validateHeaders(reqHeaders, headers);

return { req, url, timeout };
return reqHeaders;
}

/**
@@ -358,15 +371,15 @@ export abstract class APIClient {
return APIError.generate(status, error, message, headers);
}

request<Req extends {}, Rsp>(
request<Req, Rsp>(
options: PromiseOrValue<FinalRequestOptions<Req>>,
remainingRetries: number | null = null,
): APIPromise<Rsp> {
return new APIPromise(this.makeRequest(options, remainingRetries));
}

private async makeRequest(
optionsInput: PromiseOrValue<FinalRequestOptions>,
private async makeRequest<Req>(
optionsInput: PromiseOrValue<FinalRequestOptions<Req>>,
retriesRemaining: number | null,
): Promise<APIResponseProps> {
const options = await optionsInput;
@@ -428,7 +441,7 @@ export abstract class APIClient {
return new PagePromise<PageClass, Item>(this, request, Page);
}

buildURL<Req extends Record<string, unknown>>(path: string, query: Req | null | undefined): string {
buildURL<Req>(path: string, query: Req | null | undefined): string {
const url =
isAbsoluteURL(path) ?
new URL(path)
@@ -602,7 +615,7 @@ export abstract class AbstractPage<Item> implements AsyncIterable<Item> {
);
}
const nextOptions = { ...this.options };
if ('params' in nextInfo) {
if ('params' in nextInfo && typeof nextOptions.query === 'object') {
nextOptions.query = { ...nextOptions.query, ...nextInfo.params };
} else if ('url' in nextInfo) {
const params = [...Object.entries(nextOptions.query || {}), ...nextInfo.url.searchParams.entries()];
@@ -700,7 +713,7 @@ export type Headers = Record<string, string | null | undefined>;
export type DefaultQuery = Record<string, string | undefined>;
export type KeysEnum<T> = { [P in keyof Required<T>]: true };

export type RequestOptions<Req extends {} = Record<string, unknown> | Readable> = {
export type RequestOptions<Req = unknown | Record<string, unknown> | Readable> = {
method?: HTTPMethod;
path?: string;
query?: Req | undefined;
@@ -737,7 +750,7 @@ const requestOptionsKeys: KeysEnum<RequestOptions> = {
__binaryResponse: true,
};

export const isRequestOptions = (obj: unknown): obj is RequestOptions<Record<string, unknown> | Readable> => {
export const isRequestOptions = (obj: unknown): obj is RequestOptions => {
return (
typeof obj === 'object' &&
obj !== null &&
@@ -746,7 +759,7 @@ export const isRequestOptions = (obj: unknown): obj is RequestOptions<Record<str
);
};

export type FinalRequestOptions<Req extends {} = Record<string, unknown> | Readable> = RequestOptions<Req> & {
export type FinalRequestOptions<Req = unknown | Record<string, unknown> | Readable> = RequestOptions<Req> & {
method: HTTPMethod;
path: string;
};
@@ -1013,6 +1026,28 @@ export function hasOwn(obj: Object, key: string): boolean {
return Object.prototype.hasOwnProperty.call(obj, key);
}

/**
* Copies headers from "newHeaders" onto "targetHeaders",
* using lower-case for all properties,
* ignoring any keys with undefined values,
* and deleting any keys with null values.
*/
function applyHeadersMut(targetHeaders: Headers, newHeaders: Headers): void {
for (const k in newHeaders) {
if (!hasOwn(newHeaders, k)) continue;
const lowerKey = k.toLowerCase();
if (!lowerKey) continue;

const val = newHeaders[k];

if (val === null) {
delete targetHeaders[lowerKey];
} else if (val !== undefined) {
targetHeaders[lowerKey] = val;
}
}
}

export function debug(action: string, ...args: any[]) {
if (typeof process !== 'undefined' && process.env['DEBUG'] === 'true') {
console.log(`OpenAI:DEBUG:${action}`, ...args);
4 changes: 4 additions & 0 deletions src/lib/.keep
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
File generated from our OpenAPI spec by Stainless.

This directory can be used to store custom files to expand the SDK.
It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.
2 changes: 1 addition & 1 deletion src/resources/beta/assistants/assistants.ts
Original file line number Diff line number Diff line change
@@ -268,7 +268,7 @@ export interface AssistantUpdateParams {
* A list of [File](https://platform.openai.com/docs/api-reference/files) IDs
* attached to this assistant. There can be a maximum of 20 files attached to the
* assistant. Files are ordered by their creation date in ascending order. If a
* file was previosuly attached to the list but does not show up in the list, it
* file was previously attached to the list but does not show up in the list, it
* will be deleted from the assistant.
*/
file_ids?: Array<string>;
Loading