Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release: 4.24.2 #599

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 -->
Expand Down
2 changes: 1 addition & 1 deletion build-deno
Original file line number Diff line number Diff line change
Expand Up @@ -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();
\`\`\`
Expand Down
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
Expand All @@ -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'),
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/_shims/index-deno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>> {
Expand Down
2 changes: 1 addition & 1 deletion src/_shims/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>>;
Expand Down
2 changes: 1 addition & 1 deletion src/_shims/node-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>> {
Expand Down
2 changes: 1 addition & 1 deletion src/_shims/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>>;
Expand Down
2 changes: 1 addition & 1 deletion src/_shims/web-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
95 changes: 65 additions & 30 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>>,
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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,
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()];
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 &&
Expand All @@ -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;
};
Expand Down Expand Up @@ -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);
Expand Down
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
Expand Up @@ -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>;
Expand Down
Loading