From 2c3132424b3a0eeddc35b1dce3cff324b8453022 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:10:38 +0000 Subject: [PATCH] feat(api): add uploads endpoints (#946) --- .stats.yml | 4 +- api.md | 22 +++ src/index.ts | 6 + src/resources/chat/completions.ts | 1 + src/resources/index.ts | 1 + src/resources/uploads/index.ts | 4 + src/resources/uploads/parts.ts | 68 ++++++++ src/resources/uploads/uploads.ts | 169 ++++++++++++++++++++ tests/api-resources/uploads/parts.test.ts | 30 ++++ tests/api-resources/uploads/uploads.test.ts | 74 +++++++++ 10 files changed, 377 insertions(+), 2 deletions(-) create mode 100644 src/resources/uploads/index.ts create mode 100644 src/resources/uploads/parts.ts create mode 100644 src/resources/uploads/uploads.ts create mode 100644 tests/api-resources/uploads/parts.test.ts create mode 100644 tests/api-resources/uploads/uploads.test.ts diff --git a/.stats.yml b/.stats.yml index 27e2ce5ed..4e4cb5509 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ -configured_endpoints: 64 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai-518ca6c60061d3e8bc0971facf40d752f2aea62e3522cc168ad29a1f29cab3dd.yml +configured_endpoints: 68 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai-77cfff37114bc9f141c7e6107eb5f1b38d8cc99bc3d4ce03a066db2b6b649c69.yml diff --git a/api.md b/api.md index 423a067c8..044d8ff25 100644 --- a/api.md +++ b/api.md @@ -362,3 +362,25 @@ Methods: - client.batches.retrieve(batchId) -> Batch - client.batches.list({ ...params }) -> BatchesPage - client.batches.cancel(batchId) -> Batch + +# Uploads + +Types: + +- Upload + +Methods: + +- client.uploads.create({ ...params }) -> Upload +- client.uploads.cancel(uploadId) -> Upload +- client.uploads.complete(uploadId, { ...params }) -> Upload + +## Parts + +Types: + +- UploadPart + +Methods: + +- client.uploads.parts.create(uploadId, { ...params }) -> UploadPart diff --git a/src/index.ts b/src/index.ts index df4ca86d7..0c10128d5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -164,6 +164,7 @@ export class OpenAI extends Core.APIClient { fineTuning: API.FineTuning = new API.FineTuning(this); beta: API.Beta = new API.Beta(this); batches: API.Batches = new API.Batches(this); + uploads: API.Uploads = new API.Uploads(this); protected override defaultQuery(): Core.DefaultQuery | undefined { return this._options.defaultQuery; @@ -309,6 +310,11 @@ export namespace OpenAI { export import BatchCreateParams = API.BatchCreateParams; export import BatchListParams = API.BatchListParams; + export import Uploads = API.Uploads; + export import Upload = API.Upload; + export import UploadCreateParams = API.UploadCreateParams; + export import UploadCompleteParams = API.UploadCompleteParams; + export import ErrorObject = API.ErrorObject; export import FunctionDefinition = API.FunctionDefinition; export import FunctionParameters = API.FunctionParameters; diff --git a/src/resources/chat/completions.ts b/src/resources/chat/completions.ts index 44eb9520c..4027e995b 100644 --- a/src/resources/chat/completions.ts +++ b/src/resources/chat/completions.ts @@ -820,6 +820,7 @@ export interface ChatCompletionCreateParamsBase { * exhausted. * - If set to 'default', the request will be processed using the default service * tier with a lower uptime SLA and no latency guarentee. + * - When not set, the default behavior is 'auto'. * * When this parameter is set, the response body will include the `service_tier` * utilized. diff --git a/src/resources/index.ts b/src/resources/index.ts index 6f8e8564c..9f2a3cbe7 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -43,3 +43,4 @@ export { } from './images'; export { Model, ModelDeleted, ModelsPage, Models } from './models'; export { Moderation, ModerationCreateResponse, ModerationCreateParams, Moderations } from './moderations'; +export { Upload, UploadCreateParams, UploadCompleteParams, Uploads } from './uploads/uploads'; diff --git a/src/resources/uploads/index.ts b/src/resources/uploads/index.ts new file mode 100644 index 000000000..1a353d312 --- /dev/null +++ b/src/resources/uploads/index.ts @@ -0,0 +1,4 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export { Upload, UploadCreateParams, UploadCompleteParams, Uploads } from './uploads'; +export { UploadPart, PartCreateParams, Parts } from './parts'; diff --git a/src/resources/uploads/parts.ts b/src/resources/uploads/parts.ts new file mode 100644 index 000000000..a4af5c606 --- /dev/null +++ b/src/resources/uploads/parts.ts @@ -0,0 +1,68 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../resource'; +import * as Core from '../../core'; +import * as PartsAPI from './parts'; + +export class Parts extends APIResource { + /** + * Adds a + * [Part](https://platform.openai.com/docs/api-reference/uploads/part-object) to an + * [Upload](https://platform.openai.com/docs/api-reference/uploads/object) object. + * A Part represents a chunk of bytes from the file you are trying to upload. + * + * Each Part can be at most 64 MB, and you can add Parts until you hit the Upload + * maximum of 8 GB. + * + * It is possible to add multiple Parts in parallel. You can decide the intended + * order of the Parts when you + * [complete the Upload](https://platform.openai.com/docs/api-reference/uploads/complete). + */ + create( + uploadId: string, + body: PartCreateParams, + options?: Core.RequestOptions, + ): Core.APIPromise { + return this._client.post( + `/uploads/${uploadId}/parts`, + Core.multipartFormRequestOptions({ body, ...options }), + ); + } +} + +/** + * The upload Part represents a chunk of bytes we can add to an Upload object. + */ +export interface UploadPart { + /** + * The upload Part unique identifier, which can be referenced in API endpoints. + */ + id: string; + + /** + * The Unix timestamp (in seconds) for when the Part was created. + */ + created_at: number; + + /** + * The object type, which is always `upload.part`. + */ + object: 'upload.part'; + + /** + * The ID of the Upload object that this Part was added to. + */ + upload_id: string; +} + +export interface PartCreateParams { + /** + * The chunk of bytes for this Part. + */ + data: Core.Uploadable; +} + +export namespace Parts { + export import UploadPart = PartsAPI.UploadPart; + export import PartCreateParams = PartsAPI.PartCreateParams; +} diff --git a/src/resources/uploads/uploads.ts b/src/resources/uploads/uploads.ts new file mode 100644 index 000000000..ceb2b6d23 --- /dev/null +++ b/src/resources/uploads/uploads.ts @@ -0,0 +1,169 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../resource'; +import * as Core from '../../core'; +import * as UploadsAPI from './uploads'; +import * as FilesAPI from '../files'; +import * as PartsAPI from './parts'; + +export class Uploads extends APIResource { + parts: PartsAPI.Parts = new PartsAPI.Parts(this._client); + + /** + * Creates an intermediate + * [Upload](https://platform.openai.com/docs/api-reference/uploads/object) object + * that you can add + * [Parts](https://platform.openai.com/docs/api-reference/uploads/part-object) to. + * Currently, an Upload can accept at most 8 GB in total and expires after an hour + * after you create it. + * + * Once you complete the Upload, we will create a + * [File](https://platform.openai.com/docs/api-reference/files/object) object that + * contains all the parts you uploaded. This File is usable in the rest of our + * platform as a regular File object. + * + * For certain `purpose`s, the correct `mime_type` must be specified. Please refer + * to documentation for the supported MIME types for your use case: + * + * - [Assistants](https://platform.openai.com/docs/assistants/tools/file-search/supported-files) + * + * For guidance on the proper filename extensions for each purpose, please follow + * the documentation on + * [creating a File](https://platform.openai.com/docs/api-reference/files/create). + */ + create(body: UploadCreateParams, options?: Core.RequestOptions): Core.APIPromise { + return this._client.post('/uploads', { body, ...options }); + } + + /** + * Cancels the Upload. No Parts may be added after an Upload is cancelled. + */ + cancel(uploadId: string, options?: Core.RequestOptions): Core.APIPromise { + return this._client.post(`/uploads/${uploadId}/cancel`, options); + } + + /** + * Completes the + * [Upload](https://platform.openai.com/docs/api-reference/uploads/object). + * + * Within the returned Upload object, there is a nested + * [File](https://platform.openai.com/docs/api-reference/files/object) object that + * is ready to use in the rest of the platform. + * + * You can specify the order of the Parts by passing in an ordered list of the Part + * IDs. + * + * The number of bytes uploaded upon completion must match the number of bytes + * initially specified when creating the Upload object. No Parts may be added after + * an Upload is completed. + */ + complete( + uploadId: string, + body: UploadCompleteParams, + options?: Core.RequestOptions, + ): Core.APIPromise { + return this._client.post(`/uploads/${uploadId}/complete`, { body, ...options }); + } +} + +/** + * The Upload object can accept byte chunks in the form of Parts. + */ +export interface Upload { + /** + * The Upload unique identifier, which can be referenced in API endpoints. + */ + id: string; + + /** + * The intended number of bytes to be uploaded. + */ + bytes: number; + + /** + * The Unix timestamp (in seconds) for when the Upload was created. + */ + created_at: number; + + /** + * The Unix timestamp (in seconds) for when the Upload was created. + */ + expires_at: number; + + /** + * The name of the file to be uploaded. + */ + filename: string; + + /** + * The object type, which is always "upload". + */ + object: 'upload'; + + /** + * The intended purpose of the file. + * [Please refer here](https://platform.openai.com/docs/api-reference/files/object#files/object-purpose) + * for acceptable values. + */ + purpose: string; + + /** + * The status of the Upload. + */ + status: 'pending' | 'completed' | 'cancelled' | 'expired'; + + /** + * The ready File object after the Upload is completed. + */ + file?: FilesAPI.FileObject | null; +} + +export interface UploadCreateParams { + /** + * The number of bytes in the file you are uploading. + */ + bytes: number; + + /** + * The name of the file to upload. + */ + filename: string; + + /** + * The MIME type of the file. + * + * This must fall within the supported MIME types for your file purpose. See the + * supported MIME types for assistants and vision. + */ + mime_type: string; + + /** + * The intended purpose of the uploaded file. + * + * See the + * [documentation on File purposes](https://platform.openai.com/docs/api-reference/files/create#files-create-purpose). + */ + purpose: 'assistants' | 'batch' | 'fine-tune' | 'vision'; +} + +export interface UploadCompleteParams { + /** + * The ordered list of Part IDs. + */ + part_ids: Array; + + /** + * The optional md5 checksum for the file contents to verify if the bytes uploaded + * matches what you expect. + */ + md5?: string; +} + +export namespace Uploads { + export import Upload = UploadsAPI.Upload; + export import UploadCreateParams = UploadsAPI.UploadCreateParams; + export import UploadCompleteParams = UploadsAPI.UploadCompleteParams; + export import Parts = PartsAPI.Parts; + export import UploadPart = PartsAPI.UploadPart; + export import PartCreateParams = PartsAPI.PartCreateParams; +} diff --git a/tests/api-resources/uploads/parts.test.ts b/tests/api-resources/uploads/parts.test.ts new file mode 100644 index 000000000..5e69c5861 --- /dev/null +++ b/tests/api-resources/uploads/parts.test.ts @@ -0,0 +1,30 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import OpenAI, { toFile } from 'openai'; +import { Response } from 'node-fetch'; + +const openai = new OpenAI({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource parts', () => { + test('create: only required params', async () => { + const responsePromise = openai.uploads.parts.create('upload_abc123', { + data: await toFile(Buffer.from('# my file contents'), 'README.md'), + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('create: required and optional params', async () => { + const response = await openai.uploads.parts.create('upload_abc123', { + data: await toFile(Buffer.from('# my file contents'), 'README.md'), + }); + }); +}); diff --git a/tests/api-resources/uploads/uploads.test.ts b/tests/api-resources/uploads/uploads.test.ts new file mode 100644 index 000000000..08f059d1b --- /dev/null +++ b/tests/api-resources/uploads/uploads.test.ts @@ -0,0 +1,74 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import OpenAI from 'openai'; +import { Response } from 'node-fetch'; + +const openai = new OpenAI({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource uploads', () => { + test('create: only required params', async () => { + const responsePromise = openai.uploads.create({ + bytes: 0, + filename: 'filename', + mime_type: 'mime_type', + purpose: 'assistants', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('create: required and optional params', async () => { + const response = await openai.uploads.create({ + bytes: 0, + filename: 'filename', + mime_type: 'mime_type', + purpose: 'assistants', + }); + }); + + test('cancel', async () => { + const responsePromise = openai.uploads.cancel('upload_abc123'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('cancel: request options instead of params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + openai.uploads.cancel('upload_abc123', { path: '/_stainless_unknown_path' }), + ).rejects.toThrow(OpenAI.NotFoundError); + }); + + test('complete: only required params', async () => { + const responsePromise = openai.uploads.complete('upload_abc123', { + part_ids: ['string', 'string', 'string'], + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('complete: required and optional params', async () => { + const response = await openai.uploads.complete('upload_abc123', { + part_ids: ['string', 'string', 'string'], + md5: 'md5', + }); + }); +});