diff --git a/config-node.yaml b/config-node.yaml new file mode 100644 index 00000000..316893db --- /dev/null +++ b/config-node.yaml @@ -0,0 +1,12 @@ +additionalProperties: + npmName: "openai" + npmVersion: "3.3.0" + supportsES6: true + withNodeImports: false +files: + tsc-multi.mustache: + templateType: SupportingFiles + destinationFilename: tsc-multi.json + stream.mustache: + templateType: SupportingFiles + destinationFilename: stream.ts \ No newline at end of file diff --git a/openapi.yaml b/openapi.yaml index 8962cccc..698f10c1 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -8,6 +8,8 @@ servers: tags: - name: OpenAI description: The OpenAI REST API +security: + - apiKeyAuth: [] paths: /engines: get: @@ -3562,6 +3564,10 @@ components: - created_at - level - message + securitySchemes: + bearer: + type: http + scheme: bearer x-oaiMeta: groups: diff --git a/scripts/generate_sdk.py b/scripts/generate_sdk.py index 78d2c8be..448a2044 100644 --- a/scripts/generate_sdk.py +++ b/scripts/generate_sdk.py @@ -84,10 +84,14 @@ def generate_sanitized_spec(sanitized_spec_path): def generate_sdk(sanitized_spec_path, sdk_type, output_path): """Use openapi-generator to generate the SDK.""" if sdk_type == "node": + sanitized_spec_path = sanitized_spec_path.replace(" ", "\\ ") + output_path = output_path.replace(" ", "\\ ") template_override_path = os.path.join(os.path.dirname( - __file__), "../sdk-template-overrides/typescript-axios") + __file__), "../sdk-template-overrides/typescript-axios").replace(" ", "\\ ") + configuration_path = os.path.join(os.path.dirname( + __file__), "../config-node.yaml").replace(" ", "\\ ") os.system( - f"openapi-generator generate -i {sanitized_spec_path} -g typescript-axios -o {output_path} -p supportsES6=true -t {template_override_path}") + f"openapi-generator generate -i {sanitized_spec_path} -g typescript-axios -o {output_path} -t {template_override_path} -c {configuration_path}") else: print(f"Unsupported SDK type {sdk_type}, skipping SDK generation") diff --git a/sdk-template-overrides/typescript-axios/apiInner.mustache b/sdk-template-overrides/typescript-axios/apiInner.mustache index d39525bf..f5a77fe0 100644 --- a/sdk-template-overrides/typescript-axios/apiInner.mustache +++ b/sdk-template-overrides/typescript-axios/apiInner.mustache @@ -3,8 +3,17 @@ /* eslint-disable */ {{>licenseInfo}} -import globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios'; -import { Configuration } from '{{apiRelativeToRoot}}configuration'; +import type { Configuration } from '{{apiRelativeToRoot}}configuration'; +import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; +{{#withNodeImports}} +// URLSearchParams not necessarily used +// @ts-ignore +import { URL, URLSearchParams } from 'url'; +{{#multipartFormData}} +import FormData from 'form-data' +{{/multipartFormData}} +{{/withNodeImports}} // Some imports not used depending on template conditions // @ts-ignore import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from '{{apiRelativeToRoot}}common'; @@ -138,12 +147,16 @@ export const {{classname}}AxiosParamCreator = function (configuration?: Configur } {{/isArray}} {{^isArray}} - if ({{paramName}} !== undefined && {{paramName}} !== null) { + {{! `val == null` covers for both `null` and `undefined`}} + if ({{paramName}} != null) { {{#isString}} localVarHeaderParameter['{{baseName}}'] = String({{paramName}}); {{/isString}} {{^isString}} - localVarHeaderParameter['{{baseName}}'] = String(JSON.stringify({{paramName}})); + {{! isString is falsy also for $ref that defines a string or enum type}} + localVarHeaderParameter['{{baseName}}'] = typeof {{paramName}} === 'string' + ? {{paramName}} + : JSON.stringify({{paramName}}); {{/isString}} } {{/isArray}} @@ -184,8 +197,8 @@ export const {{classname}}AxiosParamCreator = function (configuration?: Configur {{/bodyParam}} setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - {{! Edited by OpenAI, line 188 }} - localVarRequestOptions.headers = {...localVarHeaderParameter,{{#vendorExtensions}}{{#multipartFormData}} ...localVarFormParams.getHeaders(),{{/multipartFormData}}{{/vendorExtensions}} ...headersFromBaseOptions, ...options.headers}; + {{! Edited by OpenAI, line 201 }} + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions,{{#vendorExtensions}}{{#multipartFormData}} ...(localVarFormParams as any).getHeaders?.(),{{/multipartFormData}}{{/vendorExtensions}} ...options.headers}; {{#hasFormParams}} localVarRequestOptions.data = localVarFormParams{{#vendorExtensions}}{{^multipartFormData}}.toString(){{/multipartFormData}}{{/vendorExtensions}}; {{/hasFormParams}} @@ -245,16 +258,30 @@ export const {{classname}}Factory = function (configuration?: Configuration, bas {{#summary}} * @summary {{&summary}} {{/summary}} + {{#useSingleRequestParameter}} + {{#allParams.0}} + * @param {{=<% %>=}}{<%& classname %><%& operationIdCamelCase %>Request}<%={{ }}=%> requestParameters Request parameters. + {{/allParams.0}} + {{/useSingleRequestParameter}} + {{^useSingleRequestParameter}} {{#allParams}} * @param {{=<% %>=}}{<%&dataType%>}<%={{ }}=%> {{^required}}[{{/required}}{{paramName}}{{^required}}]{{/required}} {{description}} {{/allParams}} + {{/useSingleRequestParameter}} * @param {*} [options] Override http request option.{{#isDeprecated}} * @deprecated{{/isDeprecated}} * @throws {RequiredError} */ + {{#useSingleRequestParameter}} + {{nickname}}({{#allParams.0}}requestParameters: {{classname}}{{operationIdCamelCase}}Request{{^hasRequiredParams}} = {}{{/hasRequiredParams}}, {{/allParams.0}}options?: AxiosRequestConfig): AxiosPromise<{{{returnType}}}{{^returnType}}void{{/returnType}}> { + return localVarFp.{{nickname}}({{#allParams.0}}{{#allParams}}requestParameters.{{paramName}}, {{/allParams}}{{/allParams.0}}options).then((request) => request(axios, basePath)); + }, + {{/useSingleRequestParameter}} + {{^useSingleRequestParameter}} {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: any): AxiosPromise<{{{returnType}}}{{^returnType}}void{{/returnType}}> { return localVarFp.{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}options).then((request) => request(axios, basePath)); }, + {{/useSingleRequestParameter}} {{/operation}} }; }; diff --git a/sdk-template-overrides/typescript-axios/configuration.mustache b/sdk-template-overrides/typescript-axios/configuration.mustache index 1020f66b..be968a04 100644 --- a/sdk-template-overrides/typescript-axios/configuration.mustache +++ b/sdk-template-overrides/typescript-axios/configuration.mustache @@ -2,8 +2,9 @@ /* eslint-disable */ {{>licenseInfo}} -{{! Edited by OpenAI, line 6 }} -const packageJson = require("../package.json"); +{{! Edited by OpenAI, line 6-7 }} +import FormData from "form-data"; +import { LIB_VERSION } from "./version"; export interface ConfigurationParameters { apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); @@ -92,7 +93,7 @@ export class Configuration { this.baseOptions = {}; } this.baseOptions.headers = { - 'User-Agent': `OpenAI/NodeJS/${packageJson.version}`, + 'User-Agent': `OpenAI/NodeJS/${LIB_VERSION}`, 'Authorization': `Bearer ${this.apiKey}`, ...this.baseOptions.headers, } @@ -100,7 +101,7 @@ export class Configuration { this.baseOptions.headers['OpenAI-Organization'] = this.organization; } if (!this.formDataCtor) { - this.formDataCtor = require("form-data"); + this.formDataCtor = FormData; } } diff --git a/sdk-template-overrides/typescript-axios/package.mustache b/sdk-template-overrides/typescript-axios/package.mustache new file mode 100644 index 00000000..167eb1f7 --- /dev/null +++ b/sdk-template-overrides/typescript-axios/package.mustache @@ -0,0 +1,68 @@ +{ + "name": "{{npmName}}", + "version": "{{npmVersion}}", + "description": "OpenAPI client for {{npmName}}", + "author": "OpenAPI-Generator Contributors", + "repository": { + "type": "git", + "url": "https://{{gitHost}}/{{gitUserId}}/{{gitRepoId}}.git" + }, + "keywords": [ + "axios", + "typescript", + "openapi-client", + "openapi-generator", + "{{npmName}}" + ], + "license": "Unlicense", + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", +{{#supportsES6}} + {{! Edited by OpenAI, line 22-44 }} + "module": "./dist/esm/index.mjs", + "sideEffects": false, + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "require": "./dist/index.js", + "default": "./dist/esm/index.mjs" + }, + "./package.json": "./package.json", + "./stream": { + "types": "./dist/stream.d.ts", + "require": "./dist/stream.js", + "default": "./dist/esm/stream.mjs" + } + }, + "typesVersions": { + "*": { + "stream": [ + "dist/stream.d.ts" + ] + } + }, +{{/supportsES6}} + "scripts": { + {{! Edited by OpenAI, line 46-48 }} + "clean": "rimraf ./dist", + "prebuild": "npm run clean && node -p \"'export const LIB_VERSION = ' + JSON.stringify(process.env.npm_package_version) + ';'\" > version.ts", + "build": "tsc {{#supportsES6}}&& tsc-multi{{/supportsES6}}", + "prepare": "npm run build" + }, + "dependencies": { + "axios": "^0.27.2", + "form-data": "^4.0.0" + }, + "devDependencies": { + "@types/node": "*", + "rimraf": "^3.0.2", + "tsc-multi": "^0.6.1", + "typescript": "^4.0" + }{{#npmRepository}},{{/npmRepository}} +{{#npmRepository}} + "publishConfig": { + "registry": "{{npmRepository}}" + } +{{/npmRepository}} +} \ No newline at end of file diff --git a/sdk-template-overrides/typescript-axios/stream.mustache b/sdk-template-overrides/typescript-axios/stream.mustache new file mode 100644 index 00000000..8b200cd4 --- /dev/null +++ b/sdk-template-overrides/typescript-axios/stream.mustache @@ -0,0 +1,29 @@ +import { Readable } from 'node:stream'; + +async function* chunksToLines(chunksAsync: AsyncIterable): AsyncIterable { + let previous = ""; + for await (const chunk of chunksAsync) { + const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); + previous += bufferChunk; + let eolIndex; + while ((eolIndex = previous.indexOf("\n")) >= 0) { + // line includes the EOL + const line = previous.slice(0, eolIndex + 1).trimEnd(); + if (line === "data: [DONE]") break; + if (line.startsWith("data: ")) yield line; + previous = previous.slice(eolIndex + 1); + } + } +} + +async function* linesToMessages(linesAsync: AsyncIterable): AsyncIterable { + for await (const line of linesAsync) { + const message = line.substring("data :".length); + + yield message; + } +} + +export async function* streamCompletion(stream: Readable): AsyncGenerator { + yield* linesToMessages(chunksToLines(stream)); +} \ No newline at end of file diff --git a/sdk-template-overrides/typescript-axios/tsc-multi.mustache b/sdk-template-overrides/typescript-axios/tsc-multi.mustache new file mode 100644 index 00000000..ce535405 --- /dev/null +++ b/sdk-template-overrides/typescript-axios/tsc-multi.mustache @@ -0,0 +1,4 @@ +{ + "targets": [{ "extname": ".mjs" }], + "projects": ["tsconfig.esm.json"] +} \ No newline at end of file diff --git a/sdk-template-overrides/typescript-axios/tsconfig.mustache b/sdk-template-overrides/typescript-axios/tsconfig.mustache new file mode 100644 index 00000000..eadec4da --- /dev/null +++ b/sdk-template-overrides/typescript-axios/tsconfig.mustache @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "declaration": true, + "target": "{{#supportsES6}}ES6{{/supportsES6}}{{^supportsES6}}ES5{{/supportsES6}}", + "module": "commonjs", + "esModuleInterop": true, + "noImplicitAny": true, + "outDir": "dist", + "rootDir": ".", + {{#supportsES6}} + "moduleResolution": "node", + {{/supportsES6}} + {{^supportsES6}} + "lib": [ + "es6", + "dom" + ], + {{/supportsES6}} + "typeRoots": [ + "node_modules/@types" + ] + }, + "exclude": [ + "dist", + "node_modules" + ] +} \ No newline at end of file