├── .github └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── license ├── package.json ├── rollup.config.js ├── src ├── api.ts ├── base.ts ├── common.ts ├── configuration.ts ├── form-data.ts ├── index.ts └── response-types.ts ├── test └── index.ts └── tsconfig.json /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Node.js v${{ matrix.nodejs }} 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | nodejs: [12, 14, 16] 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: ${{ matrix.nodejs }} 17 | 18 | - name: Install 19 | run: | 20 | npm install 21 | npm install -g nyc 22 | 23 | - name: Compiles 24 | run: npm run build 25 | 26 | - name: Test w/ Coverage 27 | run: nyc --include=src npm test 28 | 29 | - name: Report 30 | if: matrix.nodejs >= 16 31 | run: | 32 | nyc report --reporter=text-lcov > coverage.lcov 33 | bash <(curl -s https://codecov.io/bash) 34 | env: 35 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *-lock.* 4 | *.lock 5 | *.log 6 | 7 | /dist 8 | /types 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAI Edge 2 | 3 | A TypeScript module for querying OpenAI's API using `fetch` (a standard Web API) 4 | instead of `axios`. This is a drop-in replacement for the official `openai` 5 | module (which has `axios` as a dependency). 6 | 7 | As well as reducing the bundle size, removing the dependency means we can query 8 | OpenAI from edge environments. Edge functions such as Next.js Edge API Routes 9 | are very fast and, unlike lambda functions, allow streaming data to the client. 10 | 11 | The latest version of this module has feature parity with the official `v3.3.0`. 12 | 13 | > [!IMPORTANT] 14 | > **Update November 2023:** The official `openai` library uses `fetch` in v4 15 | > on edge runtimes, making `openai-edge` redundant. 16 | > 17 | > You can migrate by replacing all imports of `'openai-node'` with `'openai'` and 18 | > running `npm exec openai migrate` 19 | 20 | ## Installation 21 | 22 | ```shell 23 | yarn add openai-edge 24 | ``` 25 | 26 | or 27 | 28 | ```shell 29 | npm install openai-edge 30 | ``` 31 | 32 | ## Responses 33 | 34 | Every method returns a promise resolving to the standard `fetch` response i.e. 35 | `Promise`. Since `fetch` doesn't have built-in support for types in 36 | its response data, `openai-edge` includes an export `ResponseTypes` which you 37 | can use to assert the correct type on the JSON response: 38 | 39 | ```typescript 40 | import { Configuration, OpenAIApi, ResponseTypes } from "openai-edge" 41 | 42 | const configuration = new Configuration({ 43 | apiKey: "YOUR-API-KEY", 44 | }) 45 | const openai = new OpenAIApi(configuration) 46 | 47 | const response = await openai.createImage({ 48 | prompt: "A cute baby sea otter", 49 | size: "512x512", 50 | response_format: "url", 51 | }) 52 | 53 | const data = (await response.json()) as ResponseTypes["createImage"] 54 | 55 | const url = data.data?.[0]?.url 56 | 57 | console.log({ url }) 58 | ``` 59 | 60 | ## With Azure 61 | 62 | To use with Azure OpenAI Service you'll need to include an `api-key` header and 63 | an `api-version` query parameter: 64 | 65 | ```typescript 66 | const config = new Configuration({ 67 | apiKey: AZURE_OPENAI_API_KEY, 68 | baseOptions: { 69 | headers: { 70 | "api-key": AZURE_OPENAI_API_KEY, 71 | }, 72 | }, 73 | basePath: `https://YOUR_RESOURCE_NAME.openai.azure.com/openai/deployments/YOUR_DEPLOYMENT_NAME`, 74 | defaultQueryParams: new URLSearchParams({ 75 | "api-version": AZURE_OPENAI_API_VERSION, 76 | }), 77 | }) 78 | ``` 79 | 80 | ## Without global fetch 81 | 82 | This module has zero dependencies and it expects `fetch` to be in the global 83 | namespace (as it is in web, edge and modern Node environments). If you're 84 | running in an environment without a global `fetch` defined e.g. an older version 85 | of Node.js, please pass `fetch` when creating your instance: 86 | 87 | ```typescript 88 | import fetch from "node-fetch" 89 | 90 | const openai = new OpenAIApi(configuration, undefined, fetch) 91 | ``` 92 | 93 | ## Without global FormData 94 | 95 | This module also expects to be in an environment where `FormData` is defined. If 96 | you're running in Node.js, that means using v18 or later. 97 | 98 | ## Available methods 99 | 100 | - `cancelFineTune` 101 | - `createAnswer` 102 | - `createChatCompletion` (including support for `functions`) 103 | - `createClassification` 104 | - `createCompletion` 105 | - `createEdit` 106 | - `createEmbedding` 107 | - `createFile` 108 | - `createFineTune` 109 | - `createImage` 110 | - `createImageEdit` 111 | - `createImageVariation` 112 | - `createModeration` 113 | - `createSearch` 114 | - `createTranscription` 115 | - `createTranslation` 116 | - `deleteFile` 117 | - `deleteModel` 118 | - `downloadFile` 119 | - `listEngines` 120 | - `listFiles` 121 | - `listFineTuneEvents` 122 | - `listFineTunes` 123 | - `listModels` 124 | - `retrieveEngine` 125 | - `retrieveFile` 126 | - `retrieveFineTune` 127 | - `retrieveModel` 128 | 129 | ## Edge route handler examples 130 | 131 | Here are some sample 132 | [Next.js Edge API Routes](https://nextjs.org/docs/api-routes/edge-api-routes) 133 | using `openai-edge`. 134 | 135 | ### 1. Streaming chat with `gpt-3.5-turbo` 136 | 137 | Note that when using the `stream: true` option, OpenAI responds with 138 | [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). 139 | Here's an example 140 | [react hook to consume SSEs](https://github.com/dan-kwiat/react-hooks#useserversentevents) 141 | and here's a [full NextJS example](https://github.com/dan-kwiat/chat-gpt-clone). 142 | 143 | ```typescript 144 | import type { NextRequest } from "next/server" 145 | import { Configuration, OpenAIApi } from "openai-edge" 146 | 147 | const configuration = new Configuration({ 148 | apiKey: process.env.OPENAI_API_KEY, 149 | }) 150 | const openai = new OpenAIApi(configuration) 151 | 152 | const handler = async (req: NextRequest) => { 153 | const { searchParams } = new URL(req.url) 154 | 155 | try { 156 | const completion = await openai.createChatCompletion({ 157 | model: "gpt-3.5-turbo", 158 | messages: [ 159 | { role: "system", content: "You are a helpful assistant." }, 160 | { role: "user", content: "Who won the world series in 2020?" }, 161 | { 162 | role: "assistant", 163 | content: "The Los Angeles Dodgers won the World Series in 2020.", 164 | }, 165 | { role: "user", content: "Where was it played?" }, 166 | ], 167 | max_tokens: 7, 168 | temperature: 0, 169 | stream: true, 170 | }) 171 | 172 | return new Response(completion.body, { 173 | headers: { 174 | "Access-Control-Allow-Origin": "*", 175 | "Content-Type": "text/event-stream;charset=utf-8", 176 | "Cache-Control": "no-cache, no-transform", 177 | "X-Accel-Buffering": "no", 178 | }, 179 | }) 180 | } catch (error: any) { 181 | console.error(error) 182 | 183 | return new Response(JSON.stringify(error), { 184 | status: 400, 185 | headers: { 186 | "content-type": "application/json", 187 | }, 188 | }) 189 | } 190 | } 191 | 192 | export const config = { 193 | runtime: "edge", 194 | } 195 | 196 | export default handler 197 | ``` 198 | 199 | ### 2. Text completion with Davinci 200 | 201 | ```typescript 202 | import type { NextRequest } from "next/server" 203 | import { Configuration, OpenAIApi, ResponseTypes } from "openai-edge" 204 | 205 | const configuration = new Configuration({ 206 | apiKey: process.env.OPENAI_API_KEY, 207 | }) 208 | const openai = new OpenAIApi(configuration) 209 | 210 | const handler = async (req: NextRequest) => { 211 | const { searchParams } = new URL(req.url) 212 | 213 | try { 214 | const completion = await openai.createCompletion({ 215 | model: "text-davinci-003", 216 | prompt: searchParams.get("prompt") ?? "Say this is a test", 217 | max_tokens: 7, 218 | temperature: 0, 219 | stream: false, 220 | }) 221 | 222 | const data = (await completion.json()) as ResponseTypes["createCompletion"] 223 | 224 | return new Response(JSON.stringify(data.choices), { 225 | status: 200, 226 | headers: { 227 | "content-type": "application/json", 228 | }, 229 | }) 230 | } catch (error: any) { 231 | console.error(error) 232 | 233 | return new Response(JSON.stringify(error), { 234 | status: 400, 235 | headers: { 236 | "content-type": "application/json", 237 | }, 238 | }) 239 | } 240 | } 241 | 242 | export const config = { 243 | runtime: "edge", 244 | } 245 | 246 | export default handler 247 | ``` 248 | 249 | ### 3. Creating an Image with DALL·E 250 | 251 | ```typescript 252 | import type { NextRequest } from "next/server" 253 | import { Configuration, OpenAIApi, ResponseTypes } from "openai-edge" 254 | 255 | const configuration = new Configuration({ 256 | apiKey: process.env.OPENAI_API_KEY, 257 | }) 258 | const openai = new OpenAIApi(configuration) 259 | 260 | const handler = async (req: NextRequest) => { 261 | const { searchParams } = new URL(req.url) 262 | 263 | try { 264 | const image = await openai.createImage({ 265 | prompt: searchParams.get("prompt") ?? "A cute baby sea otter", 266 | n: 1, 267 | size: "512x512", 268 | response_format: "url", 269 | }) 270 | 271 | const data = (await image.json()) as ResponseTypes["createImage"] 272 | 273 | const url = data.data?.[0]?.url 274 | 275 | return new Response(JSON.stringify({ url }), { 276 | status: 200, 277 | headers: { 278 | "content-type": "application/json", 279 | }, 280 | }) 281 | } catch (error: any) { 282 | console.error(error) 283 | 284 | return new Response(JSON.stringify(error), { 285 | status: 400, 286 | headers: { 287 | "content-type": "application/json", 288 | }, 289 | }) 290 | } 291 | } 292 | 293 | export const config = { 294 | runtime: "edge", 295 | } 296 | 297 | export default handler 298 | ``` 299 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Dan Kwiatkowski (https://github.com/dan-kwiat) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.2.1", 3 | "name": "openai-edge", 4 | "umd:name": "openai-edge", 5 | "repository": "dan-kwiat/openai-edge", 6 | "description": "Use OpenAI's API from an edge runtime, using standard Web APIs only", 7 | "unpkg": "dist/index.min.js", 8 | "module": "dist/index.mjs", 9 | "main": "dist/index.js", 10 | "types": "types/index.d.ts", 11 | "license": "MIT", 12 | "files": [ 13 | "dist", 14 | "types" 15 | ], 16 | "exports": { 17 | ".": { 18 | "types": "./types/index.d.ts", 19 | "import": "./dist/index.mjs", 20 | "require": "./dist/index.js" 21 | }, 22 | "./package.json": "./package.json" 23 | }, 24 | "engines": { 25 | "node": ">=18" 26 | }, 27 | "scripts": { 28 | "build": "rollup -c", 29 | "prepublishOnly": "yarn build", 30 | "types": "tsc --noEmit", 31 | "test": "uvu -r tsm test" 32 | }, 33 | "keywords": [ 34 | "openai", 35 | "api", 36 | "edge", 37 | "nextjs" 38 | ], 39 | "devDependencies": { 40 | "@rollup/plugin-node-resolve": "13.1.3", 41 | "rollup": "2.66.1", 42 | "rollup-plugin-terser": "7.0.2", 43 | "rollup-plugin-typescript2": "0.27.1", 44 | "tsm": "2.2.1", 45 | "typescript": "^4.9.4", 46 | "uvu": "0.5.3" 47 | }, 48 | "dependencies": {} 49 | } 50 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import typescript from 'rollup-plugin-typescript2'; 3 | import { terser } from 'rollup-plugin-terser'; 4 | import pkg from './package.json'; 5 | 6 | export default { 7 | input: 'src/index.ts', 8 | output: [{ 9 | format: 'esm', 10 | file: pkg.module, 11 | sourcemap: false, 12 | }, { 13 | format: 'cjs', 14 | file: pkg.main, 15 | sourcemap: false, 16 | esModule: false, 17 | }, { 18 | name: pkg['umd:name'] || pkg.name, 19 | format: 'umd', 20 | file: pkg.unpkg, 21 | sourcemap: false, 22 | esModule: false, 23 | plugins: [ 24 | terser() 25 | ] 26 | }], 27 | external: [ 28 | ...require('module').builtinModules, 29 | ...Object.keys(pkg.dependencies || {}), 30 | ...Object.keys(pkg.peerDependencies || {}), 31 | ], 32 | plugins: [ 33 | resolve(), 34 | typescript({ 35 | useTsconfigDeclarationDir: true 36 | }) 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /src/base.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | * OpenAI API 5 | * APIs for sampling from and fine-tuning language models 6 | * 7 | * The version of the OpenAPI document: 1.3.0 8 | * 9 | * 10 | * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). 11 | * https://openapi-generator.tech 12 | * Do not edit the class manually. 13 | */ 14 | 15 | import type { Configuration } from "./configuration" 16 | 17 | export const BASE_PATH = "https://api.openai.com/v1".replace(/\/+$/, "") 18 | 19 | export type FetchInstance = ( 20 | input: URL | RequestInfo, 21 | init?: RequestInit 22 | ) => Promise 23 | 24 | const globalFetch = typeof fetch === "undefined" ? undefined : fetch 25 | 26 | /** 27 | * 28 | * @export 29 | */ 30 | export const COLLECTION_FORMATS = { 31 | csv: ",", 32 | ssv: " ", 33 | tsv: "\t", 34 | pipes: "|", 35 | } 36 | 37 | /** 38 | * 39 | * @export 40 | * @interface RequestArgs 41 | */ 42 | export interface RequestArgs { 43 | url: string 44 | options: RequestInit 45 | } 46 | 47 | /** 48 | * 49 | * @export 50 | * @class BaseAPI 51 | */ 52 | export class BaseAPI { 53 | protected configuration: Configuration | undefined 54 | 55 | constructor( 56 | configuration?: Configuration, 57 | protected basePath: string = BASE_PATH, 58 | protected fetch = globalFetch 59 | ) { 60 | if (configuration) { 61 | this.configuration = configuration 62 | this.basePath = configuration.basePath || this.basePath 63 | } 64 | } 65 | } 66 | 67 | /** 68 | * 69 | * @export 70 | * @class RequiredError 71 | * @extends {Error} 72 | */ 73 | export class RequiredError extends Error { 74 | constructor(public field: string, msg?: string) { 75 | super(msg) 76 | this.name = "RequiredError" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/common.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | * OpenAI API 5 | * APIs for sampling from and fine-tuning language models 6 | * 7 | * The version of the OpenAPI document: 1.3.0 8 | * 9 | * 10 | * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). 11 | * https://openapi-generator.tech 12 | * Do not edit the class manually. 13 | */ 14 | 15 | import type { Configuration } from "./configuration" 16 | import type { FetchInstance, RequestArgs } from "./base" 17 | import { RequiredError } from "./base" 18 | 19 | /** 20 | * 21 | * @export 22 | */ 23 | export const DUMMY_BASE_URL = "https://example.com" 24 | 25 | /** 26 | * 27 | * @throws {RequiredError} 28 | * @export 29 | */ 30 | export const assertParamExists = function ( 31 | functionName: string, 32 | paramName: string, 33 | paramValue: unknown 34 | ) { 35 | if (paramValue === null || paramValue === undefined) { 36 | throw new RequiredError( 37 | paramName, 38 | `Required parameter ${paramName} was null or undefined when calling ${functionName}.` 39 | ) 40 | } 41 | } 42 | 43 | /** 44 | * 45 | * @export 46 | */ 47 | export const setApiKeyToObject = async function ( 48 | object: any, 49 | keyParamName: string, 50 | configuration?: Configuration 51 | ) { 52 | if (configuration && configuration.apiKey) { 53 | const localVarApiKeyValue = 54 | typeof configuration.apiKey === "function" 55 | ? await configuration.apiKey(keyParamName) 56 | : await configuration.apiKey 57 | object[keyParamName] = localVarApiKeyValue 58 | } 59 | } 60 | 61 | /** 62 | * 63 | * @export 64 | */ 65 | export const setBasicAuthToObject = function ( 66 | object: any, 67 | configuration?: Configuration 68 | ) { 69 | if (configuration && (configuration.username || configuration.password)) { 70 | object["auth"] = { 71 | username: configuration.username, 72 | password: configuration.password, 73 | } 74 | } 75 | } 76 | 77 | /** 78 | * 79 | * @export 80 | */ 81 | export const setBearerAuthToObject = async function ( 82 | object: any, 83 | configuration?: Configuration 84 | ) { 85 | if (configuration && configuration.accessToken) { 86 | const accessToken = 87 | typeof configuration.accessToken === "function" 88 | ? await configuration.accessToken() 89 | : await configuration.accessToken 90 | object["Authorization"] = "Bearer " + accessToken 91 | } 92 | } 93 | 94 | /** 95 | * 96 | * @export 97 | */ 98 | export const setOAuthToObject = async function ( 99 | object: any, 100 | name: string, 101 | scopes: string[], 102 | configuration?: Configuration 103 | ) { 104 | if (configuration && configuration.accessToken) { 105 | const localVarAccessTokenValue = 106 | typeof configuration.accessToken === "function" 107 | ? await configuration.accessToken(name, scopes) 108 | : await configuration.accessToken 109 | object["Authorization"] = "Bearer " + localVarAccessTokenValue 110 | } 111 | } 112 | 113 | function setFlattenedQueryParams( 114 | urlSearchParams: URLSearchParams, 115 | parameter: any, 116 | key: string = "" 117 | ): void { 118 | if (parameter == null) return 119 | if (typeof parameter === "object") { 120 | if (Array.isArray(parameter)) { 121 | ;(parameter as any[]).forEach((item) => 122 | setFlattenedQueryParams(urlSearchParams, item, key) 123 | ) 124 | } else { 125 | Object.keys(parameter).forEach((currentKey) => 126 | setFlattenedQueryParams( 127 | urlSearchParams, 128 | parameter[currentKey], 129 | `${key}${key !== "" ? "." : ""}${currentKey}` 130 | ) 131 | ) 132 | } 133 | } else { 134 | if (urlSearchParams.has(key)) { 135 | urlSearchParams.append(key, parameter) 136 | } else { 137 | urlSearchParams.set(key, parameter) 138 | } 139 | } 140 | } 141 | 142 | /** 143 | * 144 | * @export 145 | */ 146 | export const setSearchParams = function (url: URL, ...objects: any[]) { 147 | const searchParams = new URLSearchParams(url.search) 148 | setFlattenedQueryParams(searchParams, objects) 149 | url.search = searchParams.toString() 150 | } 151 | 152 | /** 153 | * 154 | * @export 155 | */ 156 | export const serializeDataIfNeeded = function ( 157 | value: any, 158 | requestOptions: any, 159 | configuration?: Configuration 160 | ) { 161 | const nonString = typeof value !== "string" 162 | const needsSerialization = 163 | nonString && configuration && configuration.isJsonMime 164 | ? configuration.isJsonMime(requestOptions.headers["Content-Type"]) 165 | : nonString 166 | return needsSerialization 167 | ? JSON.stringify(value !== undefined ? value : {}) 168 | : value || "" 169 | } 170 | 171 | /** 172 | * 173 | * @export 174 | */ 175 | export const toPathString = function (url: URL) { 176 | return url.pathname + url.search + url.hash 177 | } 178 | 179 | /** 180 | * 181 | * @export 182 | */ 183 | export const createRequestFunction = function ( 184 | fetchArgs: RequestArgs, 185 | globalFetch: FetchInstance | undefined, 186 | BASE_PATH: string, 187 | configuration?: Configuration 188 | ) { 189 | return ( 190 | fetch: FetchInstance | undefined = globalFetch, 191 | basePath: string = BASE_PATH 192 | ) => { 193 | if (typeof fetch === "undefined") { 194 | throw new Error( 195 | "You must pass a fetch polyfill if you're running in an environment without a global fetch" 196 | ) 197 | } 198 | 199 | let url = fetchArgs.url 200 | if (configuration?.defaultQueryParams) { 201 | const queryPrefix = url.indexOf("?") === -1 ? "?" : "&" 202 | url += queryPrefix + configuration.defaultQueryParams 203 | } 204 | 205 | return fetch((configuration?.basePath || basePath) + url, fetchArgs.options) 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/configuration.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | * OpenAI API 5 | * APIs for sampling from and fine-tuning language models 6 | * 7 | * The version of the OpenAPI document: 1.3.0 8 | * 9 | * 10 | * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). 11 | * https://openapi-generator.tech 12 | * Do not edit the class manually. 13 | */ 14 | 15 | import { CustomFormData } from "./form-data" 16 | 17 | export interface ConfigurationParameters { 18 | apiKey?: 19 | | string 20 | | Promise 21 | | ((name: string) => string) 22 | | ((name: string) => Promise) 23 | organization?: string 24 | username?: string 25 | password?: string 26 | accessToken?: 27 | | string 28 | | Promise 29 | | ((name?: string, scopes?: string[]) => string) 30 | | ((name?: string, scopes?: string[]) => Promise) 31 | basePath?: string 32 | baseOptions?: any 33 | defaultQueryParams?: URLSearchParams 34 | formDataCtor?: new () => any 35 | } 36 | 37 | export class Configuration { 38 | /** 39 | * parameter for apiKey security 40 | * @param name security name 41 | * @memberof Configuration 42 | */ 43 | apiKey?: 44 | | string 45 | | Promise 46 | | ((name: string) => string) 47 | | ((name: string) => Promise) 48 | /** 49 | * OpenAI organization id 50 | * 51 | * @type {string} 52 | * @memberof Configuration 53 | */ 54 | organization?: string 55 | /** 56 | * parameter for basic security 57 | * 58 | * @type {string} 59 | * @memberof Configuration 60 | */ 61 | username?: string 62 | /** 63 | * parameter for basic security 64 | * 65 | * @type {string} 66 | * @memberof Configuration 67 | */ 68 | password?: string 69 | /** 70 | * parameter for oauth2 security 71 | * @param name security name 72 | * @param scopes oauth2 scope 73 | * @memberof Configuration 74 | */ 75 | accessToken?: 76 | | string 77 | | Promise 78 | | ((name?: string, scopes?: string[]) => string) 79 | | ((name?: string, scopes?: string[]) => Promise) 80 | /** 81 | * override base path 82 | * 83 | * @type {string} 84 | * @memberof Configuration 85 | */ 86 | basePath?: string 87 | /** 88 | * base options for calls 89 | * 90 | * @type {any} 91 | * @memberof Configuration 92 | */ 93 | baseOptions?: any 94 | /** 95 | * The FormData constructor that will be used to create multipart form data 96 | * requests. You can inject this here so that execution environments that 97 | * do not support the FormData class can still run the generated client. 98 | * 99 | * @type {new () => FormData} 100 | */ 101 | defaultQueryParams?: URLSearchParams 102 | formDataCtor?: new () => any 103 | 104 | constructor(param: ConfigurationParameters = {}) { 105 | this.apiKey = param.apiKey 106 | this.organization = param.organization 107 | this.username = param.username 108 | this.password = param.password 109 | this.accessToken = param.accessToken 110 | this.basePath = param.basePath 111 | this.baseOptions = param.baseOptions 112 | this.defaultQueryParams = param.defaultQueryParams 113 | this.formDataCtor = param.formDataCtor 114 | 115 | if (!this.baseOptions) { 116 | this.baseOptions = {} 117 | } 118 | this.baseOptions.headers = { 119 | // "User-Agent": `OpenAI/NodeJS/${packageJson.version}`, 120 | Authorization: `Bearer ${this.apiKey}`, 121 | ...this.baseOptions.headers, 122 | } 123 | if (this.organization) { 124 | this.baseOptions.headers["OpenAI-Organization"] = this.organization 125 | } 126 | if (!this.formDataCtor) { 127 | this.formDataCtor = CustomFormData 128 | } 129 | } 130 | 131 | /** 132 | * Check if the given MIME is a JSON MIME. 133 | * JSON MIME examples: 134 | * application/json 135 | * application/json; charset=UTF8 136 | * APPLICATION/JSON 137 | * application/vnd.company+json 138 | * @param mime - MIME (Multipurpose Internet Mail Extensions) 139 | * @return True if the given MIME is JSON, false otherwise. 140 | */ 141 | public isJsonMime(mime: string): boolean { 142 | const jsonMime: RegExp = new RegExp( 143 | "^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$", 144 | "i" 145 | ) 146 | return ( 147 | mime !== null && 148 | (jsonMime.test(mime) || 149 | mime.toLowerCase() === "application/json-patch+json") 150 | ) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/form-data.ts: -------------------------------------------------------------------------------- 1 | // This is a simplified implementation of the `form-data` module: 2 | // https://github.com/form-data/form-data 3 | // It has a `getHeaders` method (with no argument) 4 | 5 | function generateBoundary() { 6 | // This generates a 50 character boundary similar to those used by Firefox. 7 | // They are optimized for boyer-moore parsing. 8 | var boundary = "--------------------------" 9 | for (var i = 0; i < 24; i++) { 10 | boundary += Math.floor(Math.random() * 10).toString(16) 11 | } 12 | 13 | return boundary 14 | } 15 | 16 | export class CustomFormData extends FormData { 17 | private _boundary: string 18 | 19 | constructor(...args: any) { 20 | super(...args) 21 | this._boundary = generateBoundary() 22 | } 23 | 24 | getHeaders() { 25 | var formHeaders = { 26 | "content-type": "multipart/form-data; boundary=" + this._boundary, 27 | } 28 | 29 | return formHeaders 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./api" 2 | export * from "./configuration" 3 | export * from "./response-types" 4 | -------------------------------------------------------------------------------- /src/response-types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CreateAnswerResponse, 3 | CreateChatCompletionResponse, 4 | CreateClassificationResponse, 5 | CreateCompletionResponse, 6 | CreateEditResponse, 7 | CreateEmbeddingResponse, 8 | CreateModerationResponse, 9 | CreateSearchResponse, 10 | CreateTranscriptionResponse, 11 | CreateTranslationResponse, 12 | DeleteFileResponse, 13 | DeleteModelResponse, 14 | Engine, 15 | FineTune, 16 | ImagesResponse, 17 | ListEnginesResponse, 18 | ListFilesResponse, 19 | ListFineTuneEventsResponse, 20 | ListFineTunesResponse, 21 | ListModelsResponse, 22 | Model, 23 | OpenAIFile, 24 | } from "./api" 25 | 26 | export interface ResponseTypes { 27 | cancelFineTune: FineTune 28 | createAnswer: CreateAnswerResponse 29 | createChatCompletion: CreateChatCompletionResponse 30 | createClassification: CreateClassificationResponse 31 | createCompletion: CreateCompletionResponse 32 | createEdit: CreateEditResponse 33 | createEmbedding: CreateEmbeddingResponse 34 | createFile: OpenAIFile 35 | createFineTune: FineTune 36 | createImage: ImagesResponse 37 | createImageEdit: ImagesResponse 38 | createImageVariation: ImagesResponse 39 | createModeration: CreateModerationResponse 40 | createSearch: CreateSearchResponse 41 | createTranscription: CreateTranscriptionResponse 42 | createTranslation: CreateTranslationResponse 43 | deleteFile: DeleteFileResponse 44 | deleteModel: DeleteModelResponse 45 | downloadFile: string 46 | listEngines: ListEnginesResponse 47 | listFiles: ListFilesResponse 48 | listFineTuneEvents: ListFineTuneEventsResponse 49 | listFineTunes: ListFineTunesResponse 50 | listModels: ListModelsResponse 51 | retrieveEngine: Engine 52 | retrieveFile: OpenAIFile 53 | retrieveFineTune: FineTune 54 | retrieveModel: Model 55 | } 56 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | // import { suite } from 'uvu'; 2 | // import * as assert from 'uvu/assert'; 3 | // import * as math from '../src'; 4 | 5 | // const API = suite('exports'); 6 | 7 | // API('should export an object', () => { 8 | // assert.type(math, 'object'); 9 | // }); 10 | 11 | // API.run(); 12 | 13 | // // --- 14 | 15 | // const sum = suite('sum'); 16 | 17 | // sum('should be a function', () => { 18 | // assert.type(math.sum, 'function'); 19 | // }); 20 | 21 | // sum('should default to 0', () => { 22 | // assert.is(math.sum(), 0); 23 | // }); 24 | 25 | // sum('should handle one argument', () => { 26 | // assert.is(math.sum(5), 5); 27 | // assert.is(math.sum(-12), -12); 28 | // }); 29 | 30 | // sum('should handle two arguments', () => { 31 | // assert.is(math.sum(1, 2), 3); 32 | // assert.is(math.sum(11, 12), 23); 33 | 34 | // assert.is(math.sum(-1, -2), -3); 35 | // assert.is(math.sum(-11, -12), -23); 36 | 37 | // assert.is(math.sum(1, -2), -1); 38 | // assert.is(math.sum(-11, 12), 1); 39 | // }); 40 | 41 | // sum('should handle multiple arguments', () => { 42 | // assert.is(math.sum(0, 0, -1, -2, 4, 9, 10), 20); 43 | // assert.is(math.sum(1, 2, 3, 4, 5, 6), 21); 44 | // assert.is(math.sum(10, 20, 30), 60); 45 | // }); 46 | 47 | // sum.run(); 48 | 49 | // // --- 50 | 51 | // const substract = suite('substract'); 52 | 53 | // substract('should be a function', () => { 54 | // assert.type(math.substract, 'function'); 55 | // }); 56 | 57 | // substract('should default to 0', () => { 58 | // assert.is(math.substract(), 0); 59 | // }); 60 | 61 | // substract('should handle one argument', () => { 62 | // assert.is(math.substract(5), 5); 63 | // assert.is(math.substract(-12), -12); 64 | // }); 65 | 66 | // substract('should handle two arguments', () => { 67 | // assert.is(math.substract(1, 2), -1); 68 | // assert.is(math.substract(11, 12), -1); 69 | 70 | // assert.is(math.substract(-1, -2), 1); 71 | // assert.is(math.substract(-11, -12), 1); 72 | 73 | // assert.is(math.substract(1, -2), 3); 74 | // assert.is(math.substract(-11, 12), -23); 75 | // }); 76 | 77 | // substract('should handle multiple arguments', () => { 78 | // assert.is(math.substract(0, 0, -1, -2, 4, 9, 10), -20); 79 | // assert.is(math.substract(1, 2, 3, 4, 5, 6), -19); 80 | // assert.is(math.substract(10, 20, 30), -40); 81 | // }); 82 | 83 | // substract.run(); 84 | 85 | // // --- 86 | 87 | // const average = suite('average'); 88 | 89 | // average('should be a function', () => { 90 | // assert.type(math.average, 'function'); 91 | // }); 92 | 93 | // average('should default to NaN', () => { 94 | // assert.equal(math.average(), NaN); 95 | // }); 96 | 97 | // average('should handle one argument', () => { 98 | // assert.is(math.average(5), 5); 99 | // assert.is(math.average(-12), -12); 100 | // }); 101 | 102 | // average('should handle two arguments', () => { 103 | // assert.is(math.average(1, 2), 1.5); 104 | // assert.is(math.average(11, 12), 11.5); 105 | 106 | // assert.is(math.average(-1, -2), -1.5); 107 | // assert.is(math.average(-11, -12), -11.5); 108 | 109 | // assert.is(math.average(1, -2), -0.5); 110 | // assert.is(math.average(-11, 12), 0.5); 111 | // }); 112 | 113 | // average('should handle multiple arguments', () => { 114 | // assert.is(math.average(0, 0, -1, -2, 4, 9, 10), 20 / 7); 115 | // assert.is(math.average(1, 2, 3, 4, 5, 6), 21 / 6); 116 | // assert.is(math.average(10, 20, 30), 20); 117 | // }); 118 | 119 | // average.run(); 120 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "ts-node": { 3 | "transpileOnly": true, 4 | "compilerOptions": { 5 | "module": "commonjs", 6 | "sourceMap": true 7 | }, 8 | "include": ["test/**/*"] 9 | }, 10 | "compilerOptions": { 11 | "target": "esnext", 12 | "module": "esnext", 13 | "declaration": true, 14 | "declarationDir": "types", 15 | "forceConsistentCasingInFileNames": true, 16 | "moduleResolution": "node", 17 | "strictNullChecks": true, 18 | "noImplicitAny": true, 19 | "strict": true 20 | }, 21 | "include": ["@types/**/*", "src/**/*"], 22 | "exclude": ["node_modules"] 23 | } 24 | --------------------------------------------------------------------------------