diff --git a/app/package.json b/app/package.json index 0c10db4..3f85e8b 100644 --- a/app/package.json +++ b/app/package.json @@ -72,6 +72,7 @@ "nextjs-cors": "^2.1.2", "nextjs-routes": "^2.0.1", "openai": "4.0.0-beta.7", + "openpipe": "workspace:*", "pg": "^8.11.2", "pluralize": "^8.0.0", "posthog-js": "^1.75.3", @@ -100,8 +101,7 @@ "uuid": "^9.0.0", "vite-tsconfig-paths": "^4.2.0", "zod": "^3.21.4", - "zustand": "^4.3.9", - "openpipe": "workspace:*" + "zustand": "^4.3.9" }, "devDependencies": { "@openapi-contrib/openapi-schema-to-json-schema": "^4.0.5", @@ -129,6 +129,7 @@ "eslint-plugin-unused-imports": "^2.0.0", "monaco-editor": "^0.40.0", "openapi-typescript": "^6.3.4", + "openapi-typescript-codegen": "^0.25.0", "prisma": "^4.14.0", "raw-loader": "^4.0.2", "typescript": "^5.0.4", diff --git a/app/src/modelProviders/openai-ChatCompletion/getCompletion.ts b/app/src/modelProviders/openai-ChatCompletion/getCompletion.ts index e6f5123..fcfed1f 100644 --- a/app/src/modelProviders/openai-ChatCompletion/getCompletion.ts +++ b/app/src/modelProviders/openai-ChatCompletion/getCompletion.ts @@ -1,54 +1,10 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */ -import { - type ChatCompletionChunk, - type ChatCompletion, - type CompletionCreateParams, -} from "openai/resources/chat"; -import { type CompletionResponse } from "../types"; -import { isArray, isString, omit } from "lodash-es"; -import { openai } from "~/server/utils/openai"; +import { isArray, isString } from "lodash-es"; import { APIError } from "openai"; - -const mergeStreamedChunks = ( - base: ChatCompletion | null, - chunk: ChatCompletionChunk, -): ChatCompletion => { - if (base === null) { - return mergeStreamedChunks({ ...chunk, choices: [] }, chunk); - } - - const choices = [...base.choices]; - for (const choice of chunk.choices) { - const baseChoice = choices.find((c) => c.index === choice.index); - if (baseChoice) { - baseChoice.finish_reason = choice.finish_reason ?? baseChoice.finish_reason; - baseChoice.message = baseChoice.message ?? { role: "assistant" }; - - if (choice.delta?.content) - baseChoice.message.content = - ((baseChoice.message.content as string) ?? "") + (choice.delta.content ?? ""); - if (choice.delta?.function_call) { - const fnCall = baseChoice.message.function_call ?? {}; - fnCall.name = - ((fnCall.name as string) ?? "") + ((choice.delta.function_call.name as string) ?? ""); - fnCall.arguments = - ((fnCall.arguments as string) ?? "") + - ((choice.delta.function_call.arguments as string) ?? ""); - } - } else { - // @ts-expect-error the types are correctly telling us that finish_reason - // could be null, but don't want to fix it right now. - choices.push({ ...omit(choice, "delta"), message: { role: "assistant", ...choice.delta } }); - } - } - - const merged: ChatCompletion = { - ...base, - choices, - }; - - return merged; -}; +import { type ChatCompletion, type CompletionCreateParams } from "openai/resources/chat"; +import mergeChunks from "openpipe/src/openai/mergeChunks"; +import { openai } from "~/server/utils/openai"; +import { type CompletionResponse } from "../types"; export async function getCompletion( input: CompletionCreateParams, @@ -59,7 +15,6 @@ export async function getCompletion( try { if (onStream) { - console.log("got started"); const resp = await openai.chat.completions.create( { ...input, stream: true }, { @@ -67,11 +22,9 @@ export async function getCompletion( }, ); for await (const part of resp) { - console.log("got part", part); - finalCompletion = mergeStreamedChunks(finalCompletion, part); + finalCompletion = mergeChunks(finalCompletion, part); onStream(finalCompletion); } - console.log("got final", finalCompletion); if (!finalCompletion) { return { type: "error", diff --git a/app/src/server/api/external/v1Api.router.ts b/app/src/server/api/external/v1Api.router.ts index 739eef0..557430a 100644 --- a/app/src/server/api/external/v1Api.router.ts +++ b/app/src/server/api/external/v1Api.router.ts @@ -107,7 +107,7 @@ export const v1ApiRouter = createOpenApiRouter({ .default({}), }), ) - .output(z.void()) + .output(z.object({ status: z.literal("ok") })) .mutation(async ({ input, ctx }) => { const reqPayload = await reqValidator.spa(input.reqPayload); const respPayload = await respValidator.spa(input.respPayload); @@ -166,6 +166,7 @@ export const v1ApiRouter = createOpenApiRouter({ ]); await createTags(newLoggedCallId, input.tags); + return { status: "ok" }; }), localTestingOnlyGetLatestLoggedCall: openApiProtectedProc .meta({ diff --git a/app/src/server/scripts/client-codegen.ts b/app/src/server/scripts/client-codegen.ts index 6fa9355..da8f2f6 100644 --- a/app/src/server/scripts/client-codegen.ts +++ b/app/src/server/scripts/client-codegen.ts @@ -3,6 +3,7 @@ import { openApiDocument } from "~/pages/api/v1/openapi.json"; import fs from "fs"; import path from "path"; import { execSync } from "child_process"; +import { generate } from "openapi-typescript-codegen"; const scriptPath = import.meta.url.replace("file://", ""); const clientLibsPath = path.join(path.dirname(scriptPath), "../../../../client-libs"); @@ -18,13 +19,20 @@ console.log("Generating TypeScript client"); const tsClientPath = path.join(clientLibsPath, "typescript/src/codegen"); fs.rmSync(tsClientPath, { recursive: true, force: true }); +fs.mkdirSync(tsClientPath, { recursive: true }); -execSync( - `pnpm dlx @openapitools/openapi-generator-cli generate -i "${schemaPath}" -g typescript-axios -o "${tsClientPath}"`, - { - stdio: "inherit", - }, -); +await generate({ + input: openApiDocument, + output: tsClientPath, + clientName: "OPClient", + httpClient: "node", +}); +// execSync( +// `pnpm run openapi generate --input "${schemaPath}" --output "${tsClientPath}" --name OPClient --client node`, +// { +// stdio: "inherit", +// }, +// ); console.log("Generating Python client"); diff --git a/app/src/server/utils/openai.ts b/app/src/server/utils/openai.ts index 6b0efbd..e8eaac0 100644 --- a/app/src/server/utils/openai.ts +++ b/app/src/server/utils/openai.ts @@ -1,7 +1,6 @@ -import { type ClientOptions } from "openai"; import fs from "fs"; import path from "path"; -import OpenAI from "openpipe/src/openai"; +import OpenAI, { type ClientOptions } from "openpipe/src/openai"; import { env } from "~/env.mjs"; @@ -16,7 +15,13 @@ try { config = JSON.parse(jsonData.toString()); } catch (error) { // Set a dummy key so it doesn't fail at build time - config = { apiKey: env.OPENAI_API_KEY ?? "dummy-key" }; + config = { + apiKey: env.OPENAI_API_KEY ?? "dummy-key", + openpipe: { + apiKey: env.OPENPIPE_API_KEY, + baseUrl: "http://localhost:3000/api/v1", + }, + }; } // export const openai = env.OPENPIPE_API_KEY ? new OpenAI.OpenAI(config) : new OriginalOpenAI(config); diff --git a/client-libs/openapi.json b/client-libs/openapi.json index fa54b6f..34f39b8 100644 --- a/client-libs/openapi.json +++ b/client-libs/openapi.json @@ -137,7 +137,21 @@ "description": "Successful response", "content": { "application/json": { - "schema": {} + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "ok" + ] + } + }, + "required": [ + "status" + ], + "additionalProperties": false + } } } }, diff --git a/client-libs/python/openpipe/api_client/api/default/report.py b/client-libs/python/openpipe/api_client/api/default/report.py index b1a94f4..0ee841d 100644 --- a/client-libs/python/openpipe/api_client/api/default/report.py +++ b/client-libs/python/openpipe/api_client/api/default/report.py @@ -6,6 +6,7 @@ import httpx from ... import errors from ...client import AuthenticatedClient, Client from ...models.report_json_body import ReportJsonBody +from ...models.report_response_200 import ReportResponse200 from ...types import Response @@ -24,16 +25,22 @@ def _get_kwargs( } -def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: +def _parse_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Optional[ReportResponse200]: if response.status_code == HTTPStatus.OK: - return None + response_200 = ReportResponse200.from_dict(response.json()) + + return response_200 if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: return None -def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]: +def _build_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Response[ReportResponse200]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, @@ -46,7 +53,7 @@ def sync_detailed( *, client: AuthenticatedClient, json_body: ReportJsonBody, -) -> Response[Any]: +) -> Response[ReportResponse200]: """Report an API call Args: @@ -57,7 +64,7 @@ def sync_detailed( httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Any] + Response[ReportResponse200] """ kwargs = _get_kwargs( @@ -71,11 +78,11 @@ def sync_detailed( return _build_response(client=client, response=response) -async def asyncio_detailed( +def sync( *, client: AuthenticatedClient, json_body: ReportJsonBody, -) -> Response[Any]: +) -> Optional[ReportResponse200]: """Report an API call Args: @@ -86,7 +93,31 @@ async def asyncio_detailed( httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: - Response[Any] + ReportResponse200 + """ + + return sync_detailed( + client=client, + json_body=json_body, + ).parsed + + +async def asyncio_detailed( + *, + client: AuthenticatedClient, + json_body: ReportJsonBody, +) -> Response[ReportResponse200]: + """Report an API call + + Args: + json_body (ReportJsonBody): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[ReportResponse200] """ kwargs = _get_kwargs( @@ -96,3 +127,29 @@ async def asyncio_detailed( response = await client.get_async_httpx_client().request(**kwargs) return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: AuthenticatedClient, + json_body: ReportJsonBody, +) -> Optional[ReportResponse200]: + """Report an API call + + Args: + json_body (ReportJsonBody): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + ReportResponse200 + """ + + return ( + await asyncio_detailed( + client=client, + json_body=json_body, + ) + ).parsed diff --git a/client-libs/python/openpipe/api_client/models/__init__.py b/client-libs/python/openpipe/api_client/models/__init__.py index f14034d..08a4e6c 100644 --- a/client-libs/python/openpipe/api_client/models/__init__.py +++ b/client-libs/python/openpipe/api_client/models/__init__.py @@ -12,6 +12,8 @@ from .local_testing_only_get_latest_logged_call_response_200_tags import ( ) from .report_json_body import ReportJsonBody from .report_json_body_tags import ReportJsonBodyTags +from .report_response_200 import ReportResponse200 +from .report_response_200_status import ReportResponse200Status __all__ = ( "CheckCacheJsonBody", @@ -22,4 +24,6 @@ __all__ = ( "LocalTestingOnlyGetLatestLoggedCallResponse200Tags", "ReportJsonBody", "ReportJsonBodyTags", + "ReportResponse200", + "ReportResponse200Status", ) diff --git a/client-libs/python/openpipe/api_client/models/report_response_200.py b/client-libs/python/openpipe/api_client/models/report_response_200.py new file mode 100644 index 0000000..f315863 --- /dev/null +++ b/client-libs/python/openpipe/api_client/models/report_response_200.py @@ -0,0 +1,40 @@ +from typing import Any, Dict, Type, TypeVar + +from attrs import define + +from ..models.report_response_200_status import ReportResponse200Status + +T = TypeVar("T", bound="ReportResponse200") + + +@define +class ReportResponse200: + """ + Attributes: + status (ReportResponse200Status): + """ + + status: ReportResponse200Status + + def to_dict(self) -> Dict[str, Any]: + status = self.status.value + + field_dict: Dict[str, Any] = {} + field_dict.update( + { + "status": status, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + status = ReportResponse200Status(d.pop("status")) + + report_response_200 = cls( + status=status, + ) + + return report_response_200 diff --git a/client-libs/python/openpipe/api_client/models/report_response_200_status.py b/client-libs/python/openpipe/api_client/models/report_response_200_status.py new file mode 100644 index 0000000..f6e7fd4 --- /dev/null +++ b/client-libs/python/openpipe/api_client/models/report_response_200_status.py @@ -0,0 +1,8 @@ +from enum import Enum + + +class ReportResponse200Status(str, Enum): + OK = "ok" + + def __str__(self) -> str: + return str(self.value) diff --git a/client-libs/python/openpipe/shared.py b/client-libs/python/openpipe/shared.py index d0d5e85..cf3876d 100644 --- a/client-libs/python/openpipe/shared.py +++ b/client-libs/python/openpipe/shared.py @@ -19,7 +19,7 @@ configured_client = AuthenticatedClient( def _get_tags(openpipe_options): tags = openpipe_options.get("tags") or {} tags["$sdk"] = "python" - tags["$sdk_version"] = version + tags["$sdk.version"] = version return ReportJsonBodyTags.from_dict(tags) diff --git a/client-libs/python/openpipe/test_client.py b/client-libs/python/openpipe/test_client.py index 60982d8..7bd9603 100644 --- a/client-libs/python/openpipe/test_client.py +++ b/client-libs/python/openpipe/test_client.py @@ -156,7 +156,7 @@ async def test_caching(): completion2 = await openai.ChatCompletion.acreate( model="gpt-3.5-turbo", - messages=[{"role": "system", "content": "count to 10"}], + messages=messages, openpipe={"cache": True}, ) assert completion2.openpipe.cache_status == "HIT" diff --git a/client-libs/typescript/index.ts b/client-libs/typescript/index.ts index 04c7df2..2e78298 100644 --- a/client-libs/typescript/index.ts +++ b/client-libs/typescript/index.ts @@ -1,3 +1,3 @@ // main.ts or index.ts at the root level -export * as OpenAI from './openai'; -export * as OpenAILegacy from './openai-legacy'; +export * as OpenAI from "./src/openai"; +export * as OpenAILegacy from "./src/openai-legacy"; diff --git a/client-libs/typescript/package.json b/client-libs/typescript/package.json index b2a5d67..9a5129c 100644 --- a/client-libs/typescript/package.json +++ b/client-libs/typescript/package.json @@ -4,7 +4,8 @@ "type": "module", "description": "Metrics and auto-evaluation for LLM calls", "scripts": { - "build": "tsc" + "build": "tsc", + "test": "vitest" }, "main": "dist/index.js", "types": "dist/index.d.ts", @@ -12,7 +13,8 @@ "author": "", "license": "Apache-2.0", "dependencies": { - "axios": "^0.26.0", + "form-data": "^4.0.0", + "node-fetch": "^3.3.2", "openai-beta": "npm:openai@4.0.0-beta.7", "openai-legacy": "npm:openai@3.3.0" }, @@ -20,6 +22,7 @@ "@types/node": "^20.4.8", "dotenv": "^16.3.1", "tsx": "^3.12.7", - "typescript": "^5.0.4" + "typescript": "^5.0.4", + "vitest": "^0.33.0" } } diff --git a/client-libs/typescript/src/codegen/.gitignore b/client-libs/typescript/src/codegen/.gitignore deleted file mode 100644 index 149b576..0000000 --- a/client-libs/typescript/src/codegen/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -wwwroot/*.js -node_modules -typings -dist diff --git a/client-libs/typescript/src/codegen/.npmignore b/client-libs/typescript/src/codegen/.npmignore deleted file mode 100644 index 999d88d..0000000 --- a/client-libs/typescript/src/codegen/.npmignore +++ /dev/null @@ -1 +0,0 @@ -# empty npmignore to ensure all required files (e.g., in the dist folder) are published by npm \ No newline at end of file diff --git a/client-libs/typescript/src/codegen/.openapi-generator-ignore b/client-libs/typescript/src/codegen/.openapi-generator-ignore deleted file mode 100644 index 7484ee5..0000000 --- a/client-libs/typescript/src/codegen/.openapi-generator-ignore +++ /dev/null @@ -1,23 +0,0 @@ -# OpenAPI Generator Ignore -# Generated by openapi-generator https://github.com/openapitools/openapi-generator - -# Use this file to prevent files from being overwritten by the generator. -# The patterns follow closely to .gitignore or .dockerignore. - -# As an example, the C# client generator defines ApiClient.cs. -# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: -#ApiClient.cs - -# You can match any string of characters against a directory, file or extension with a single asterisk (*): -#foo/*/qux -# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux - -# You can recursively match patterns against a directory, file or extension with a double asterisk (**): -#foo/**/qux -# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux - -# You can also negate patterns with an exclamation (!). -# For example, you can ignore all files in a docs folder with the file extension .md: -#docs/*.md -# Then explicitly reverse the ignore rule for a single file: -#!docs/README.md diff --git a/client-libs/typescript/src/codegen/.openapi-generator/FILES b/client-libs/typescript/src/codegen/.openapi-generator/FILES deleted file mode 100644 index 16b445e..0000000 --- a/client-libs/typescript/src/codegen/.openapi-generator/FILES +++ /dev/null @@ -1,9 +0,0 @@ -.gitignore -.npmignore -.openapi-generator-ignore -api.ts -base.ts -common.ts -configuration.ts -git_push.sh -index.ts diff --git a/client-libs/typescript/src/codegen/.openapi-generator/VERSION b/client-libs/typescript/src/codegen/.openapi-generator/VERSION deleted file mode 100644 index cd802a1..0000000 --- a/client-libs/typescript/src/codegen/.openapi-generator/VERSION +++ /dev/null @@ -1 +0,0 @@ -6.6.0 \ No newline at end of file diff --git a/client-libs/typescript/src/codegen/OPClient.ts b/client-libs/typescript/src/codegen/OPClient.ts new file mode 100644 index 0000000..49bdc21 --- /dev/null +++ b/client-libs/typescript/src/codegen/OPClient.ts @@ -0,0 +1,35 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { BaseHttpRequest } from './core/BaseHttpRequest'; +import type { OpenAPIConfig } from './core/OpenAPI'; +import { NodeHttpRequest } from './core/NodeHttpRequest'; + +import { DefaultService } from './services/DefaultService'; + +type HttpRequestConstructor = new (config: OpenAPIConfig) => BaseHttpRequest; + +export class OPClient { + + public readonly default: DefaultService; + + public readonly request: BaseHttpRequest; + + constructor(config?: Partial, HttpRequest: HttpRequestConstructor = NodeHttpRequest) { + this.request = new HttpRequest({ + BASE: config?.BASE ?? 'https://app.openpipe.ai/api/v1', + VERSION: config?.VERSION ?? '0.1.1', + WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false, + CREDENTIALS: config?.CREDENTIALS ?? 'include', + TOKEN: config?.TOKEN, + USERNAME: config?.USERNAME, + PASSWORD: config?.PASSWORD, + HEADERS: config?.HEADERS, + ENCODE_PATH: config?.ENCODE_PATH, + }); + + this.default = new DefaultService(this.request); + } +} + diff --git a/client-libs/typescript/src/codegen/api.ts b/client-libs/typescript/src/codegen/api.ts deleted file mode 100644 index fb4f4f3..0000000 --- a/client-libs/typescript/src/codegen/api.ts +++ /dev/null @@ -1,455 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * OpenPipe API - * The public API for reporting API calls to OpenPipe - * - * The version of the OpenAPI document: 0.1.1 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - - -import type { Configuration } from './configuration'; -import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios'; -import globalAxios from 'axios'; -// Some imports not used depending on template conditions -// @ts-ignore -import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common'; -import type { RequestArgs } from './base'; -// @ts-ignore -import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError } from './base'; - -/** - * - * @export - * @interface CheckCache200Response - */ -export interface CheckCache200Response { - /** - * JSON-encoded response payload - * @type {any} - * @memberof CheckCache200Response - */ - 'respPayload'?: any; -} -/** - * - * @export - * @interface CheckCacheDefaultResponse - */ -export interface CheckCacheDefaultResponse { - /** - * - * @type {string} - * @memberof CheckCacheDefaultResponse - */ - 'message': string; - /** - * - * @type {string} - * @memberof CheckCacheDefaultResponse - */ - 'code': string; - /** - * - * @type {Array} - * @memberof CheckCacheDefaultResponse - */ - 'issues'?: Array; -} -/** - * - * @export - * @interface CheckCacheDefaultResponseIssuesInner - */ -export interface CheckCacheDefaultResponseIssuesInner { - /** - * - * @type {string} - * @memberof CheckCacheDefaultResponseIssuesInner - */ - 'message': string; -} -/** - * - * @export - * @interface CheckCacheRequest - */ -export interface CheckCacheRequest { - /** - * Unix timestamp in milliseconds - * @type {number} - * @memberof CheckCacheRequest - */ - 'requestedAt': number; - /** - * JSON-encoded request payload - * @type {any} - * @memberof CheckCacheRequest - */ - 'reqPayload'?: any; - /** - * Extra tags to attach to the call for filtering. Eg { \"userId\": \"123\", \"promptId\": \"populate-title\" } - * @type {{ [key: string]: string; }} - * @memberof CheckCacheRequest - */ - 'tags'?: { [key: string]: string; }; -} -/** - * - * @export - * @interface LocalTestingOnlyGetLatestLoggedCall200Response - */ -export interface LocalTestingOnlyGetLatestLoggedCall200Response { - /** - * - * @type {string} - * @memberof LocalTestingOnlyGetLatestLoggedCall200Response - */ - 'createdAt': string; - /** - * - * @type {boolean} - * @memberof LocalTestingOnlyGetLatestLoggedCall200Response - */ - 'cacheHit': boolean; - /** - * - * @type {{ [key: string]: string; }} - * @memberof LocalTestingOnlyGetLatestLoggedCall200Response - */ - 'tags': { [key: string]: string; }; - /** - * - * @type {LocalTestingOnlyGetLatestLoggedCall200ResponseModelResponse} - * @memberof LocalTestingOnlyGetLatestLoggedCall200Response - */ - 'modelResponse': LocalTestingOnlyGetLatestLoggedCall200ResponseModelResponse | null; -} -/** - * - * @export - * @interface LocalTestingOnlyGetLatestLoggedCall200ResponseModelResponse - */ -export interface LocalTestingOnlyGetLatestLoggedCall200ResponseModelResponse { - /** - * - * @type {string} - * @memberof LocalTestingOnlyGetLatestLoggedCall200ResponseModelResponse - */ - 'id': string; - /** - * - * @type {number} - * @memberof LocalTestingOnlyGetLatestLoggedCall200ResponseModelResponse - */ - 'statusCode': number | null; - /** - * - * @type {string} - * @memberof LocalTestingOnlyGetLatestLoggedCall200ResponseModelResponse - */ - 'errorMessage': string | null; - /** - * - * @type {any} - * @memberof LocalTestingOnlyGetLatestLoggedCall200ResponseModelResponse - */ - 'reqPayload'?: any; - /** - * - * @type {any} - * @memberof LocalTestingOnlyGetLatestLoggedCall200ResponseModelResponse - */ - 'respPayload'?: any; -} -/** - * - * @export - * @interface ReportRequest - */ -export interface ReportRequest { - /** - * Unix timestamp in milliseconds - * @type {number} - * @memberof ReportRequest - */ - 'requestedAt': number; - /** - * Unix timestamp in milliseconds - * @type {number} - * @memberof ReportRequest - */ - 'receivedAt': number; - /** - * JSON-encoded request payload - * @type {any} - * @memberof ReportRequest - */ - 'reqPayload'?: any; - /** - * JSON-encoded response payload - * @type {any} - * @memberof ReportRequest - */ - 'respPayload'?: any; - /** - * HTTP status code of response - * @type {number} - * @memberof ReportRequest - */ - 'statusCode'?: number; - /** - * User-friendly error message - * @type {string} - * @memberof ReportRequest - */ - 'errorMessage'?: string; - /** - * Extra tags to attach to the call for filtering. Eg { \"userId\": \"123\", \"promptId\": \"populate-title\" } - * @type {{ [key: string]: string; }} - * @memberof ReportRequest - */ - 'tags'?: { [key: string]: string; }; -} - -/** - * DefaultApi - axios parameter creator - * @export - */ -export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) { - return { - /** - * Check if a prompt is cached - * @param {CheckCacheRequest} checkCacheRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - checkCache: async (checkCacheRequest: CheckCacheRequest, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'checkCacheRequest' is not null or undefined - assertParamExists('checkCache', 'checkCacheRequest', checkCacheRequest) - const localVarPath = `/check-cache`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication Authorization required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - localVarHeaderParameter['Content-Type'] = 'application/json'; - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - localVarRequestOptions.data = serializeDataIfNeeded(checkCacheRequest, localVarRequestOptions, configuration) - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - /** - * Get the latest logged call (only for local testing) - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - localTestingOnlyGetLatestLoggedCall: async (options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/local-testing-only-get-latest-logged-call`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication Authorization required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - /** - * Report an API call - * @param {ReportRequest} reportRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - report: async (reportRequest: ReportRequest, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'reportRequest' is not null or undefined - assertParamExists('report', 'reportRequest', reportRequest) - const localVarPath = `/report`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication Authorization required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - localVarHeaderParameter['Content-Type'] = 'application/json'; - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - localVarRequestOptions.data = serializeDataIfNeeded(reportRequest, localVarRequestOptions, configuration) - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - } -}; - -/** - * DefaultApi - functional programming interface - * @export - */ -export const DefaultApiFp = function(configuration?: Configuration) { - const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration) - return { - /** - * Check if a prompt is cached - * @param {CheckCacheRequest} checkCacheRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async checkCache(checkCacheRequest: CheckCacheRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.checkCache(checkCacheRequest, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, - /** - * Get the latest logged call (only for local testing) - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async localTestingOnlyGetLatestLoggedCall(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.localTestingOnlyGetLatestLoggedCall(options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, - /** - * Report an API call - * @param {ReportRequest} reportRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async report(reportRequest: ReportRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.report(reportRequest, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, - } -}; - -/** - * DefaultApi - factory interface - * @export - */ -export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { - const localVarFp = DefaultApiFp(configuration) - return { - /** - * Check if a prompt is cached - * @param {CheckCacheRequest} checkCacheRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - checkCache(checkCacheRequest: CheckCacheRequest, options?: any): AxiosPromise { - return localVarFp.checkCache(checkCacheRequest, options).then((request) => request(axios, basePath)); - }, - /** - * Get the latest logged call (only for local testing) - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - localTestingOnlyGetLatestLoggedCall(options?: any): AxiosPromise { - return localVarFp.localTestingOnlyGetLatestLoggedCall(options).then((request) => request(axios, basePath)); - }, - /** - * Report an API call - * @param {ReportRequest} reportRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - report(reportRequest: ReportRequest, options?: any): AxiosPromise { - return localVarFp.report(reportRequest, options).then((request) => request(axios, basePath)); - }, - }; -}; - -/** - * DefaultApi - object-oriented interface - * @export - * @class DefaultApi - * @extends {BaseAPI} - */ -export class DefaultApi extends BaseAPI { - /** - * Check if a prompt is cached - * @param {CheckCacheRequest} checkCacheRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof DefaultApi - */ - public checkCache(checkCacheRequest: CheckCacheRequest, options?: AxiosRequestConfig) { - return DefaultApiFp(this.configuration).checkCache(checkCacheRequest, options).then((request) => request(this.axios, this.basePath)); - } - - /** - * Get the latest logged call (only for local testing) - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof DefaultApi - */ - public localTestingOnlyGetLatestLoggedCall(options?: AxiosRequestConfig) { - return DefaultApiFp(this.configuration).localTestingOnlyGetLatestLoggedCall(options).then((request) => request(this.axios, this.basePath)); - } - - /** - * Report an API call - * @param {ReportRequest} reportRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof DefaultApi - */ - public report(reportRequest: ReportRequest, options?: AxiosRequestConfig) { - return DefaultApiFp(this.configuration).report(reportRequest, options).then((request) => request(this.axios, this.basePath)); - } -} - - diff --git a/client-libs/typescript/src/codegen/base.ts b/client-libs/typescript/src/codegen/base.ts deleted file mode 100644 index f81a406..0000000 --- a/client-libs/typescript/src/codegen/base.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * OpenPipe API - * The public API for reporting API calls to OpenPipe - * - * The version of the OpenAPI document: 0.1.1 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - - -import type { Configuration } from './configuration'; -// Some imports not used depending on template conditions -// @ts-ignore -import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios'; -import globalAxios from 'axios'; - -export const BASE_PATH = "https://app.openpipe.ai/api/v1".replace(/\/+$/, ""); - -/** - * - * @export - */ -export const COLLECTION_FORMATS = { - csv: ",", - ssv: " ", - tsv: "\t", - pipes: "|", -}; - -/** - * - * @export - * @interface RequestArgs - */ -export interface RequestArgs { - url: string; - options: AxiosRequestConfig; -} - -/** - * - * @export - * @class BaseAPI - */ -export class BaseAPI { - protected configuration: Configuration | undefined; - - constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) { - if (configuration) { - this.configuration = configuration; - this.basePath = configuration.basePath || this.basePath; - } - } -}; - -/** - * - * @export - * @class RequiredError - * @extends {Error} - */ -export class RequiredError extends Error { - constructor(public field: string, msg?: string) { - super(msg); - this.name = "RequiredError" - } -} diff --git a/client-libs/typescript/src/codegen/common.ts b/client-libs/typescript/src/codegen/common.ts deleted file mode 100644 index 8ad7e0e..0000000 --- a/client-libs/typescript/src/codegen/common.ts +++ /dev/null @@ -1,150 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * OpenPipe API - * The public API for reporting API calls to OpenPipe - * - * The version of the OpenAPI document: 0.1.1 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - - -import type { Configuration } from "./configuration"; -import type { RequestArgs } from "./base"; -import type { AxiosInstance, AxiosResponse } from 'axios'; -import { RequiredError } from "./base"; - -/** - * - * @export - */ -export const DUMMY_BASE_URL = 'https://example.com' - -/** - * - * @throws {RequiredError} - * @export - */ -export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) { - if (paramValue === null || paramValue === undefined) { - throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`); - } -} - -/** - * - * @export - */ -export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) { - if (configuration && configuration.apiKey) { - const localVarApiKeyValue = typeof configuration.apiKey === 'function' - ? await configuration.apiKey(keyParamName) - : await configuration.apiKey; - object[keyParamName] = localVarApiKeyValue; - } -} - -/** - * - * @export - */ -export const setBasicAuthToObject = function (object: any, configuration?: Configuration) { - if (configuration && (configuration.username || configuration.password)) { - object["auth"] = { username: configuration.username, password: configuration.password }; - } -} - -/** - * - * @export - */ -export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) { - if (configuration && configuration.accessToken) { - const accessToken = typeof configuration.accessToken === 'function' - ? await configuration.accessToken() - : await configuration.accessToken; - object["Authorization"] = "Bearer " + accessToken; - } -} - -/** - * - * @export - */ -export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) { - if (configuration && configuration.accessToken) { - const localVarAccessTokenValue = typeof configuration.accessToken === 'function' - ? await configuration.accessToken(name, scopes) - : await configuration.accessToken; - object["Authorization"] = "Bearer " + localVarAccessTokenValue; - } -} - -function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void { - if (parameter == null) return; - if (typeof parameter === "object") { - if (Array.isArray(parameter)) { - (parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key)); - } - else { - Object.keys(parameter).forEach(currentKey => - setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`) - ); - } - } - else { - if (urlSearchParams.has(key)) { - urlSearchParams.append(key, parameter); - } - else { - urlSearchParams.set(key, parameter); - } - } -} - -/** - * - * @export - */ -export const setSearchParams = function (url: URL, ...objects: any[]) { - const searchParams = new URLSearchParams(url.search); - setFlattenedQueryParams(searchParams, objects); - url.search = searchParams.toString(); -} - -/** - * - * @export - */ -export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) { - const nonString = typeof value !== 'string'; - const needsSerialization = nonString && configuration && configuration.isJsonMime - ? configuration.isJsonMime(requestOptions.headers['Content-Type']) - : nonString; - return needsSerialization - ? JSON.stringify(value !== undefined ? value : {}) - : (value || ""); -} - -/** - * - * @export - */ -export const toPathString = function (url: URL) { - return url.pathname + url.search + url.hash -} - -/** - * - * @export - */ -export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) { - return >(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { - const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url}; - return axios.request(axiosRequestArgs); - }; -} diff --git a/client-libs/typescript/src/codegen/configuration.ts b/client-libs/typescript/src/codegen/configuration.ts deleted file mode 100644 index bb6a089..0000000 --- a/client-libs/typescript/src/codegen/configuration.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * OpenPipe API - * The public API for reporting API calls to OpenPipe - * - * The version of the OpenAPI document: 0.1.1 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - - -export interface ConfigurationParameters { - apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); - username?: string; - password?: string; - accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); - basePath?: string; - baseOptions?: any; - formDataCtor?: new () => any; -} - -export class Configuration { - /** - * parameter for apiKey security - * @param name security name - * @memberof Configuration - */ - apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); - /** - * parameter for basic security - * - * @type {string} - * @memberof Configuration - */ - username?: string; - /** - * parameter for basic security - * - * @type {string} - * @memberof Configuration - */ - password?: string; - /** - * parameter for oauth2 security - * @param name security name - * @param scopes oauth2 scope - * @memberof Configuration - */ - accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); - /** - * override base path - * - * @type {string} - * @memberof Configuration - */ - basePath?: string; - /** - * base options for axios calls - * - * @type {any} - * @memberof Configuration - */ - baseOptions?: any; - /** - * The FormData constructor that will be used to create multipart form data - * requests. You can inject this here so that execution environments that - * do not support the FormData class can still run the generated client. - * - * @type {new () => FormData} - */ - formDataCtor?: new () => any; - - constructor(param: ConfigurationParameters = {}) { - this.apiKey = param.apiKey; - this.username = param.username; - this.password = param.password; - this.accessToken = param.accessToken; - this.basePath = param.basePath; - this.baseOptions = param.baseOptions; - this.formDataCtor = param.formDataCtor; - } - - /** - * Check if the given MIME is a JSON MIME. - * JSON MIME examples: - * application/json - * application/json; charset=UTF8 - * APPLICATION/JSON - * application/vnd.company+json - * @param mime - MIME (Multipurpose Internet Mail Extensions) - * @return True if the given MIME is JSON, false otherwise. - */ - public isJsonMime(mime: string): boolean { - const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i'); - return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json'); - } -} diff --git a/client-libs/typescript/src/codegen/core/ApiError.ts b/client-libs/typescript/src/codegen/core/ApiError.ts new file mode 100644 index 0000000..d6b8fcc --- /dev/null +++ b/client-libs/typescript/src/codegen/core/ApiError.ts @@ -0,0 +1,25 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; + +export class ApiError extends Error { + public readonly url: string; + public readonly status: number; + public readonly statusText: string; + public readonly body: any; + public readonly request: ApiRequestOptions; + + constructor(request: ApiRequestOptions, response: ApiResult, message: string) { + super(message); + + this.name = 'ApiError'; + this.url = response.url; + this.status = response.status; + this.statusText = response.statusText; + this.body = response.body; + this.request = request; + } +} diff --git a/client-libs/typescript/src/codegen/core/ApiRequestOptions.ts b/client-libs/typescript/src/codegen/core/ApiRequestOptions.ts new file mode 100644 index 0000000..c19adcc --- /dev/null +++ b/client-libs/typescript/src/codegen/core/ApiRequestOptions.ts @@ -0,0 +1,17 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ApiRequestOptions = { + readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; + readonly url: string; + readonly path?: Record; + readonly cookies?: Record; + readonly headers?: Record; + readonly query?: Record; + readonly formData?: Record; + readonly body?: any; + readonly mediaType?: string; + readonly responseHeader?: string; + readonly errors?: Record; +}; diff --git a/client-libs/typescript/src/codegen/core/ApiResult.ts b/client-libs/typescript/src/codegen/core/ApiResult.ts new file mode 100644 index 0000000..ad8fef2 --- /dev/null +++ b/client-libs/typescript/src/codegen/core/ApiResult.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ApiResult = { + readonly url: string; + readonly ok: boolean; + readonly status: number; + readonly statusText: string; + readonly body: any; +}; diff --git a/client-libs/typescript/src/codegen/core/BaseHttpRequest.ts b/client-libs/typescript/src/codegen/core/BaseHttpRequest.ts new file mode 100644 index 0000000..8da3f4d --- /dev/null +++ b/client-libs/typescript/src/codegen/core/BaseHttpRequest.ts @@ -0,0 +1,14 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { CancelablePromise } from './CancelablePromise'; +import type { OpenAPIConfig } from './OpenAPI'; + +export abstract class BaseHttpRequest { + + constructor(public readonly config: OpenAPIConfig) {} + + public abstract request(options: ApiRequestOptions): CancelablePromise; +} diff --git a/client-libs/typescript/src/codegen/core/CancelablePromise.ts b/client-libs/typescript/src/codegen/core/CancelablePromise.ts new file mode 100644 index 0000000..55fef85 --- /dev/null +++ b/client-libs/typescript/src/codegen/core/CancelablePromise.ts @@ -0,0 +1,131 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export class CancelError extends Error { + + constructor(message: string) { + super(message); + this.name = 'CancelError'; + } + + public get isCancelled(): boolean { + return true; + } +} + +export interface OnCancel { + readonly isResolved: boolean; + readonly isRejected: boolean; + readonly isCancelled: boolean; + + (cancelHandler: () => void): void; +} + +export class CancelablePromise implements Promise { + #isResolved: boolean; + #isRejected: boolean; + #isCancelled: boolean; + readonly #cancelHandlers: (() => void)[]; + readonly #promise: Promise; + #resolve?: (value: T | PromiseLike) => void; + #reject?: (reason?: any) => void; + + constructor( + executor: ( + resolve: (value: T | PromiseLike) => void, + reject: (reason?: any) => void, + onCancel: OnCancel + ) => void + ) { + this.#isResolved = false; + this.#isRejected = false; + this.#isCancelled = false; + this.#cancelHandlers = []; + this.#promise = new Promise((resolve, reject) => { + this.#resolve = resolve; + this.#reject = reject; + + const onResolve = (value: T | PromiseLike): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isResolved = true; + this.#resolve?.(value); + }; + + const onReject = (reason?: any): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isRejected = true; + this.#reject?.(reason); + }; + + const onCancel = (cancelHandler: () => void): void => { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#cancelHandlers.push(cancelHandler); + }; + + Object.defineProperty(onCancel, 'isResolved', { + get: (): boolean => this.#isResolved, + }); + + Object.defineProperty(onCancel, 'isRejected', { + get: (): boolean => this.#isRejected, + }); + + Object.defineProperty(onCancel, 'isCancelled', { + get: (): boolean => this.#isCancelled, + }); + + return executor(onResolve, onReject, onCancel as OnCancel); + }); + } + + get [Symbol.toStringTag]() { + return "Cancellable Promise"; + } + + public then( + onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, + onRejected?: ((reason: any) => TResult2 | PromiseLike) | null + ): Promise { + return this.#promise.then(onFulfilled, onRejected); + } + + public catch( + onRejected?: ((reason: any) => TResult | PromiseLike) | null + ): Promise { + return this.#promise.catch(onRejected); + } + + public finally(onFinally?: (() => void) | null): Promise { + return this.#promise.finally(onFinally); + } + + public cancel(): void { + if (this.#isResolved || this.#isRejected || this.#isCancelled) { + return; + } + this.#isCancelled = true; + if (this.#cancelHandlers.length) { + try { + for (const cancelHandler of this.#cancelHandlers) { + cancelHandler(); + } + } catch (error) { + console.warn('Cancellation threw an error', error); + return; + } + } + this.#cancelHandlers.length = 0; + this.#reject?.(new CancelError('Request aborted')); + } + + public get isCancelled(): boolean { + return this.#isCancelled; + } +} diff --git a/client-libs/typescript/src/codegen/core/NodeHttpRequest.ts b/client-libs/typescript/src/codegen/core/NodeHttpRequest.ts new file mode 100644 index 0000000..680e6a9 --- /dev/null +++ b/client-libs/typescript/src/codegen/core/NodeHttpRequest.ts @@ -0,0 +1,26 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; +import { BaseHttpRequest } from './BaseHttpRequest'; +import type { CancelablePromise } from './CancelablePromise'; +import type { OpenAPIConfig } from './OpenAPI'; +import { request as __request } from './request'; + +export class NodeHttpRequest extends BaseHttpRequest { + + constructor(config: OpenAPIConfig) { + super(config); + } + + /** + * Request method + * @param options The request options from the service + * @returns CancelablePromise + * @throws ApiError + */ + public override request(options: ApiRequestOptions): CancelablePromise { + return __request(this.config, options); + } +} diff --git a/client-libs/typescript/src/codegen/core/OpenAPI.ts b/client-libs/typescript/src/codegen/core/OpenAPI.ts new file mode 100644 index 0000000..3510db4 --- /dev/null +++ b/client-libs/typescript/src/codegen/core/OpenAPI.ts @@ -0,0 +1,32 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ApiRequestOptions } from './ApiRequestOptions'; + +type Resolver = (options: ApiRequestOptions) => Promise; +type Headers = Record; + +export type OpenAPIConfig = { + BASE: string; + VERSION: string; + WITH_CREDENTIALS: boolean; + CREDENTIALS: 'include' | 'omit' | 'same-origin'; + TOKEN?: string | Resolver | undefined; + USERNAME?: string | Resolver | undefined; + PASSWORD?: string | Resolver | undefined; + HEADERS?: Headers | Resolver | undefined; + ENCODE_PATH?: ((path: string) => string) | undefined; +}; + +export const OpenAPI: OpenAPIConfig = { + BASE: 'https://app.openpipe.ai/api/v1', + VERSION: '0.1.1', + WITH_CREDENTIALS: false, + CREDENTIALS: 'include', + TOKEN: undefined, + USERNAME: undefined, + PASSWORD: undefined, + HEADERS: undefined, + ENCODE_PATH: undefined, +}; diff --git a/client-libs/typescript/src/codegen/core/request.ts b/client-libs/typescript/src/codegen/core/request.ts new file mode 100644 index 0000000..f742ade --- /dev/null +++ b/client-libs/typescript/src/codegen/core/request.ts @@ -0,0 +1,341 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import FormData from "form-data"; +import fetch, { Headers } from "node-fetch"; +import type { RequestInit, Response } from "node-fetch"; + +// @ts-expect-error TODO maybe I need an older node-fetch or something? +import type { AbortSignal } from "node-fetch/externals"; + +import { ApiError } from "./ApiError"; +import type { ApiRequestOptions } from "./ApiRequestOptions"; +import type { ApiResult } from "./ApiResult"; +import { CancelablePromise } from "./CancelablePromise"; +import type { OnCancel } from "./CancelablePromise"; +import type { OpenAPIConfig } from "./OpenAPI"; + +export const isDefined = ( + value: T | null | undefined +): value is Exclude => { + return value !== undefined && value !== null; +}; + +export const isString = (value: any): value is string => { + return typeof value === "string"; +}; + +export const isStringWithValue = (value: any): value is string => { + return isString(value) && value !== ""; +}; + +export const isBlob = (value: any): value is Blob => { + return ( + typeof value === "object" && + typeof value.type === "string" && + typeof value.stream === "function" && + typeof value.arrayBuffer === "function" && + typeof value.constructor === "function" && + typeof value.constructor.name === "string" && + /^(Blob|File)$/.test(value.constructor.name) && + /^(Blob|File)$/.test(value[Symbol.toStringTag]) + ); +}; + +export const isFormData = (value: any): value is FormData => { + return value instanceof FormData; +}; + +export const base64 = (str: string): string => { + try { + return btoa(str); + } catch (err) { + // @ts-ignore + return Buffer.from(str).toString("base64"); + } +}; + +export const getQueryString = (params: Record): string => { + const qs: string[] = []; + + const append = (key: string, value: any) => { + qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); + }; + + const process = (key: string, value: any) => { + if (isDefined(value)) { + if (Array.isArray(value)) { + value.forEach((v) => { + process(key, v); + }); + } else if (typeof value === "object") { + Object.entries(value).forEach(([k, v]) => { + process(`${key}[${k}]`, v); + }); + } else { + append(key, value); + } + } + }; + + Object.entries(params).forEach(([key, value]) => { + process(key, value); + }); + + if (qs.length > 0) { + return `?${qs.join("&")}`; + } + + return ""; +}; + +const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { + const encoder = config.ENCODE_PATH || encodeURI; + + const path = options.url + .replace("{api-version}", config.VERSION) + .replace(/{(.*?)}/g, (substring: string, group: string) => { + if (options.path?.hasOwnProperty(group)) { + return encoder(String(options.path[group])); + } + return substring; + }); + + const url = `${config.BASE}${path}`; + if (options.query) { + return `${url}${getQueryString(options.query)}`; + } + return url; +}; + +export const getFormData = (options: ApiRequestOptions): FormData | undefined => { + if (options.formData) { + const formData = new FormData(); + + const process = (key: string, value: any) => { + if (isString(value) || isBlob(value)) { + formData.append(key, value); + } else { + formData.append(key, JSON.stringify(value)); + } + }; + + Object.entries(options.formData) + .filter(([_, value]) => isDefined(value)) + .forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach((v) => process(key, v)); + } else { + process(key, value); + } + }); + + return formData; + } + return undefined; +}; + +type Resolver = (options: ApiRequestOptions) => Promise; + +export const resolve = async ( + options: ApiRequestOptions, + resolver?: T | Resolver +): Promise => { + if (typeof resolver === "function") { + return (resolver as Resolver)(options); + } + return resolver; +}; + +export const getHeaders = async ( + config: OpenAPIConfig, + options: ApiRequestOptions +): Promise => { + const token = await resolve(options, config.TOKEN); + const username = await resolve(options, config.USERNAME); + const password = await resolve(options, config.PASSWORD); + const additionalHeaders = await resolve(options, config.HEADERS); + + const headers = Object.entries({ + Accept: "application/json", + ...additionalHeaders, + ...options.headers, + }) + .filter(([_, value]) => isDefined(value)) + .reduce( + (headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), + {} as Record + ); + + if (isStringWithValue(token)) { + headers["Authorization"] = `Bearer ${token}`; + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`); + headers["Authorization"] = `Basic ${credentials}`; + } + + if (options.body) { + if (options.mediaType) { + headers["Content-Type"] = options.mediaType; + } else if (isBlob(options.body)) { + headers["Content-Type"] = "application/octet-stream"; + } else if (isString(options.body)) { + headers["Content-Type"] = "text/plain"; + } else if (!isFormData(options.body)) { + headers["Content-Type"] = "application/json"; + } + } + + return new Headers(headers); +}; + +export const getRequestBody = (options: ApiRequestOptions): any => { + if (options.body !== undefined) { + if (options.mediaType?.includes("/json")) { + return JSON.stringify(options.body); + } else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) { + return options.body as any; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +}; + +export const sendRequest = async ( + options: ApiRequestOptions, + url: string, + body: any, + formData: FormData | undefined, + headers: Headers, + onCancel: OnCancel +): Promise => { + const controller = new AbortController(); + + const request: RequestInit = { + headers, + method: options.method, + body: body ?? formData, + signal: controller.signal as AbortSignal, + }; + + onCancel(() => controller.abort()); + + return await fetch(url, request); +}; + +export const getResponseHeader = ( + response: Response, + responseHeader?: string +): string | undefined => { + if (responseHeader) { + const content = response.headers.get(responseHeader); + if (isString(content)) { + return content; + } + } + return undefined; +}; + +export const getResponseBody = async (response: Response): Promise => { + if (response.status !== 204) { + try { + const contentType = response.headers.get("Content-Type"); + if (contentType) { + const jsonTypes = ["application/json", "application/problem+json"]; + const isJSON = jsonTypes.some((type) => contentType.toLowerCase().startsWith(type)); + if (isJSON) { + return await response.json(); + } else { + return await response.text(); + } + } + } catch (error) { + console.error(error); + } + } + return undefined; +}; + +export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { + const errors: Record = { + 400: "Bad Request", + 401: "Unauthorized", + 403: "Forbidden", + 404: "Not Found", + 500: "Internal Server Error", + 502: "Bad Gateway", + 503: "Service Unavailable", + ...options.errors, + }; + + const error = errors[result.status]; + if (error) { + throw new ApiError(options, result, error); + } + + if (!result.ok) { + const errorStatus = result.status ?? "unknown"; + const errorStatusText = result.statusText ?? "unknown"; + const errorBody = (() => { + try { + return JSON.stringify(result.body, null, 2); + } catch (e) { + return undefined; + } + })(); + + throw new ApiError( + options, + result, + `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` + ); + } +}; + +/** + * Request method + * @param config The OpenAPI configuration object + * @param options The request options from the service + * @returns CancelablePromise + * @throws ApiError + */ +export const request = ( + config: OpenAPIConfig, + options: ApiRequestOptions +): CancelablePromise => { + return new CancelablePromise(async (resolve, reject, onCancel) => { + try { + const url = getUrl(config, options); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(config, options); + + if (!onCancel.isCancelled) { + const response = await sendRequest(options, url, body, formData, headers, onCancel); + const responseBody = await getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + const result: ApiResult = { + url, + ok: response.ok, + status: response.status, + statusText: response.statusText, + body: responseHeader ?? responseBody, + }; + + catchErrorCodes(options, result); + + resolve(result.body); + } + } catch (error) { + reject(error); + } + }); +}; diff --git a/client-libs/typescript/src/codegen/git_push.sh b/client-libs/typescript/src/codegen/git_push.sh deleted file mode 100644 index f53a75d..0000000 --- a/client-libs/typescript/src/codegen/git_push.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/sh -# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ -# -# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" - -git_user_id=$1 -git_repo_id=$2 -release_note=$3 -git_host=$4 - -if [ "$git_host" = "" ]; then - git_host="github.com" - echo "[INFO] No command line input provided. Set \$git_host to $git_host" -fi - -if [ "$git_user_id" = "" ]; then - git_user_id="GIT_USER_ID" - echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" -fi - -if [ "$git_repo_id" = "" ]; then - git_repo_id="GIT_REPO_ID" - echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" -fi - -if [ "$release_note" = "" ]; then - release_note="Minor update" - echo "[INFO] No command line input provided. Set \$release_note to $release_note" -fi - -# Initialize the local directory as a Git repository -git init - -# Adds the files in the local repository and stages them for commit. -git add . - -# Commits the tracked changes and prepares them to be pushed to a remote repository. -git commit -m "$release_note" - -# Sets the new remote -git_remote=$(git remote) -if [ "$git_remote" = "" ]; then # git remote not defined - - if [ "$GIT_TOKEN" = "" ]; then - echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." - git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git - else - git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git - fi - -fi - -git pull origin master - -# Pushes (Forces) the changes in the local repository up to the remote repository -echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" -git push origin master 2>&1 | grep -v 'To https' diff --git a/client-libs/typescript/src/codegen/index.ts b/client-libs/typescript/src/codegen/index.ts index c3e73e1..5f1504b 100644 --- a/client-libs/typescript/src/codegen/index.ts +++ b/client-libs/typescript/src/codegen/index.ts @@ -1,18 +1,13 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -/** - * OpenPipe API - * The public API for reporting API calls to OpenPipe - * - * The version of the OpenAPI document: 0.1.1 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ +export { OPClient } from './OPClient'; +export { ApiError } from './core/ApiError'; +export { BaseHttpRequest } from './core/BaseHttpRequest'; +export { CancelablePromise, CancelError } from './core/CancelablePromise'; +export { OpenAPI } from './core/OpenAPI'; +export type { OpenAPIConfig } from './core/OpenAPI'; -export * from "./api"; -export * from "./configuration"; - +export { DefaultService } from './services/DefaultService'; diff --git a/client-libs/typescript/src/codegen/services/DefaultService.ts b/client-libs/typescript/src/codegen/services/DefaultService.ts new file mode 100644 index 0000000..bf565a3 --- /dev/null +++ b/client-libs/typescript/src/codegen/services/DefaultService.ts @@ -0,0 +1,118 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { CancelablePromise } from '../core/CancelablePromise'; +import type { BaseHttpRequest } from '../core/BaseHttpRequest'; + +export class DefaultService { + + constructor(public readonly httpRequest: BaseHttpRequest) {} + + /** + * Check if a prompt is cached + * @param requestBody + * @returns any Successful response + * @throws ApiError + */ + public checkCache( + requestBody: { + /** + * Unix timestamp in milliseconds + */ + requestedAt: number; + /** + * JSON-encoded request payload + */ + reqPayload?: any; + /** + * Extra tags to attach to the call for filtering. Eg { "userId": "123", "promptId": "populate-title" } + */ + tags?: Record; + }, + ): CancelablePromise<{ + /** + * JSON-encoded response payload + */ + respPayload?: any; + }> { + return this.httpRequest.request({ + method: 'POST', + url: '/check-cache', + body: requestBody, + mediaType: 'application/json', + }); + } + + /** + * Report an API call + * @param requestBody + * @returns any Successful response + * @throws ApiError + */ + public report( + requestBody: { + /** + * Unix timestamp in milliseconds + */ + requestedAt: number; + /** + * Unix timestamp in milliseconds + */ + receivedAt: number; + /** + * JSON-encoded request payload + */ + reqPayload?: any; + /** + * JSON-encoded response payload + */ + respPayload?: any; + /** + * HTTP status code of response + */ + statusCode?: number; + /** + * User-friendly error message + */ + errorMessage?: string; + /** + * Extra tags to attach to the call for filtering. Eg { "userId": "123", "promptId": "populate-title" } + */ + tags?: Record; + }, + ): CancelablePromise<{ + status: 'ok'; + }> { + return this.httpRequest.request({ + method: 'POST', + url: '/report', + body: requestBody, + mediaType: 'application/json', + }); + } + + /** + * Get the latest logged call (only for local testing) + * @returns any Successful response + * @throws ApiError + */ + public localTestingOnlyGetLatestLoggedCall(): CancelablePromise<{ + createdAt: string; + cacheHit: boolean; + tags: Record; + modelResponse: { + id: string; + statusCode: number | null; + errorMessage: string | null; + reqPayload?: any; + respPayload?: any; + } | null; + } | null> { + return this.httpRequest.request({ + method: 'GET', + url: '/local-testing-only-get-latest-logged-call', + }); + } + +} diff --git a/client-libs/typescript/src/openai-legacy/index.ts b/client-libs/typescript/src/openai-legacy/index.ts index 965c12c..355462d 100644 --- a/client-libs/typescript/src/openai-legacy/index.ts +++ b/client-libs/typescript/src/openai-legacy/index.ts @@ -1,90 +1,85 @@ -// import * as openPipeClient from "../codegen"; -// import * as openai from "openai-legacy"; -// import { version } from "../package.json"; +import * as openPipeClient from "../codegen"; +import * as openai from "openai-legacy"; +import { version } from "../../package.json"; -// // Anything we don't override we want to pass through to openai directly -// export * as openAILegacy from "openai-legacy"; +// Anything we don't override we want to pass through to openai directly +export * as openAILegacy from "openai-legacy"; -// type OPConfigurationParameters = { -// apiKey?: string; -// basePath?: string; -// }; +type OPConfigurationParameters = { + apiKey?: string; + basePath?: string; +}; -// export class Configuration extends openai.Configuration { -// public qkConfig?: openPipeClient.Configuration; +export class Configuration extends openai.Configuration { + public qkConfig?: openPipeClient.Configuration; -// constructor( -// config: openai.ConfigurationParameters & { -// opParameters?: OPConfigurationParameters; -// } -// ) { -// super(config); -// if (config.opParameters) { -// this.qkConfig = new openPipeClient.Configuration(config.opParameters); -// } -// } -// } + constructor( + config: openai.ConfigurationParameters & { + opParameters?: OPConfigurationParameters; + } + ) { + super(config); + if (config.opParameters) { + this.qkConfig = new openPipeClient.Configuration(config.opParameters); + } + } +} -// type CreateChatCompletion = InstanceType< -// typeof openai.OpenAIApi -// >["createChatCompletion"]; +type CreateChatCompletion = InstanceType["createChatCompletion"]; -// export class OpenAIApi extends openai.OpenAIApi { -// public openPipeApi?: openPipeClient.DefaultApi; +export class OpenAIApi extends openai.OpenAIApi { + public openPipeApi?: openPipeClient.DefaultApi; -// constructor(config: Configuration) { -// super(config); -// if (config.qkConfig) { -// this.openPipeApi = new openPipeClient.DefaultApi(config.qkConfig); -// } -// } + constructor(config: Configuration) { + super(config); + if (config.qkConfig) { + this.openPipeApi = new openPipeClient.DefaultApi(config.qkConfig); + } + } -// public async createChatCompletion( -// createChatCompletionRequest: Parameters[0], -// options?: Parameters[1] -// ): ReturnType { -// const requestedAt = Date.now(); -// let resp: Awaited> | null = null; -// let respPayload: openai.CreateChatCompletionResponse | null = null; -// let statusCode: number | undefined = undefined; -// let errorMessage: string | undefined; -// try { -// resp = await super.createChatCompletion( -// createChatCompletionRequest, -// options -// ); -// respPayload = resp.data; -// statusCode = resp.status; -// } catch (err) { -// console.error("Error in createChatCompletion"); -// if ("isAxiosError" in err && err.isAxiosError) { -// errorMessage = err.response?.data?.error?.message; -// respPayload = err.response?.data; -// statusCode = err.response?.status; -// } else if ("message" in err) { -// errorMessage = err.message.toString(); -// } -// throw err; -// } finally { -// this.openPipeApi -// ?.externalApiReport({ -// requestedAt, -// receivedAt: Date.now(), -// reqPayload: createChatCompletionRequest, -// respPayload: respPayload, -// statusCode: statusCode, -// errorMessage, -// tags: { -// client: "openai-js", -// clientVersion: version, -// }, -// }) -// .catch((err) => { -// console.error("Error reporting to OP", err); -// }); -// } + public async createChatCompletion( + createChatCompletionRequest: Parameters[0], + options?: Parameters[1] + ): ReturnType { + const requestedAt = Date.now(); + let resp: Awaited> | null = null; + let respPayload: openai.CreateChatCompletionResponse | null = null; + let statusCode: number | undefined = undefined; + let errorMessage: string | undefined; + try { + resp = await super.createChatCompletion(createChatCompletionRequest, options); + respPayload = resp.data; + statusCode = resp.status; + } catch (err) { + console.error("Error in createChatCompletion"); + if ("isAxiosError" in err && err.isAxiosError) { + errorMessage = err.response?.data?.error?.message; + respPayload = err.response?.data; + statusCode = err.response?.status; + } else if ("message" in err) { + errorMessage = err.message.toString(); + } + throw err; + } finally { + this.openPipeApi + ?.externalApiReport({ + requestedAt, + receivedAt: Date.now(), + reqPayload: createChatCompletionRequest, + respPayload: respPayload, + statusCode: statusCode, + errorMessage, + tags: { + client: "openai-js", + clientVersion: version, + }, + }) + .catch((err) => { + console.error("Error reporting to OP", err); + }); + } -// console.log("done"); -// return resp; -// } -// } + console.log("done"); + return resp; + } +} diff --git a/client-libs/typescript/src/openai/index.test.ts b/client-libs/typescript/src/openai/index.test.ts new file mode 100644 index 0000000..48c22cc --- /dev/null +++ b/client-libs/typescript/src/openai/index.test.ts @@ -0,0 +1,126 @@ +import dotenv from "dotenv"; +import { expect, test } from "vitest"; +import OpenAI from "."; +import { + CompletionCreateParams, + CreateChatCompletionRequestMessage, +} from "openai-beta/resources/chat/completions"; +import { OPClient } from "../codegen"; + +dotenv.config({ path: "../.env" }); + +const oaiClient = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, + openpipe: { + apiKey: process.env.OPENPIPE_API_KEY, + baseUrl: "http://localhost:3000/api/v1", + }, +}); + +const opClient = new OPClient({ + BASE: "http://localhost:3000/api/v1", + TOKEN: process.env.OPENPIPE_API_KEY, +}); + +const lastLoggedCall = async () => opClient.default.localTestingOnlyGetLatestLoggedCall(); + +test("basic call", async () => { + const payload: CompletionCreateParams = { + model: "gpt-3.5-turbo", + messages: [{ role: "system", content: "count to 3" }], + }; + const completion = await oaiClient.chat.completions.create({ + ...payload, + openpipe: { + tags: { promptId: "test" }, + }, + }); + await completion.openpipe.reportingFinished; + const lastLogged = await lastLoggedCall(); + expect(lastLogged?.modelResponse?.reqPayload).toMatchObject(payload); + expect(completion).toMatchObject(lastLogged?.modelResponse?.respPayload); + expect(lastLogged?.tags).toMatchObject({ promptId: "test" }); +}); + +const randomString = (length: number) => { + const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + return Array.from( + { length }, + () => characters[Math.floor(Math.random() * characters.length)] + ).join(""); +}; + +test.skip("streaming", async () => { + const completion = await oaiClient.chat.completions.create({ + model: "gpt-3.5-turbo", + messages: [{ role: "system", content: "count to 4" }], + stream: true, + }); + + let merged = null; + for await (const chunk of completion) { + merged = merge_openai_chunks(merged, chunk); + } + + const lastLogged = await lastLoggedCall(); + expect(lastLogged?.modelResponse?.respPayload.choices[0].message.content).toBe( + merged.choices[0].message.content + ); +}); + +test.skip("bad call streaming", async () => { + try { + await oaiClient.chat.completions.create({ + model: "gpt-3.5-turbo-blaster", + messages: [{ role: "system", content: "count to 10" }], + stream: true, + }); + } catch (e) { + const lastLogged = await lastLoggedCall(); + expect(lastLogged?.modelResponse?.errorMessage).toBe( + "The model `gpt-3.5-turbo-blaster` does not exist" + ); + expect(lastLogged?.modelResponse?.statusCode).toBe(404); + } +}); + +test("bad call", async () => { + try { + await oaiClient.chat.completions.create({ + model: "gpt-3.5-turbo-booster", + messages: [{ role: "system", content: "count to 10" }], + }); + } catch (e) { + const lastLogged = await lastLoggedCall(); + expect(lastLogged?.modelResponse?.errorMessage).toBe( + "The model `gpt-3.5-turbo-booster` does not exist" + ); + expect(lastLogged?.modelResponse?.statusCode).toBe(404); + } +}); + +test("caching", async () => { + const message: CreateChatCompletionRequestMessage = { + role: "system", + content: `repeat '${randomString(10)}'`, + }; + const completion = await oaiClient.chat.completions.create({ + model: "gpt-3.5-turbo", + messages: [message], + openpipe: { cache: true }, + }); + expect(completion.openpipe.cacheStatus).toBe("MISS"); + + await completion.openpipe.reportingFinished; + const firstLogged = await lastLoggedCall(); + expect(completion.choices[0].message.content).toBe( + firstLogged?.modelResponse?.respPayload.choices[0].message.content + ); + + const completion2 = await oaiClient.chat.completions.create({ + model: "gpt-3.5-turbo", + messages: [message], + openpipe: { cache: true }, + }); + expect(completion2.openpipe.cacheStatus).toBe("HIT"); +}); diff --git a/client-libs/typescript/src/openai/index.ts b/client-libs/typescript/src/openai/index.ts index ca1e231..aff67a9 100644 --- a/client-libs/typescript/src/openai/index.ts +++ b/client-libs/typescript/src/openai/index.ts @@ -1,109 +1,154 @@ import * as openai from "openai-beta"; +import * as Core from "openai-beta/core"; import { readEnv, type RequestOptions } from "openai-beta/core"; -import { CompletionCreateParams } from "openai-beta/resources/chat/completions"; -import axios from "axios"; +import { + ChatCompletion, + ChatCompletionChunk, + CompletionCreateParams, + Completions, +} from "openai-beta/resources/chat/completions"; -import * as openPipeClient from "../codegen"; - -interface ClientOptions extends openai.ClientOptions { - openPipeApiKey?: string; - openPipeBaseUrl?: string; -} +import { DefaultService, OPClient } from "../codegen"; +import { Stream } from "openai-beta/streaming"; +import { OpenPipeArgs, OpenPipeMeta, type OpenPipeConfig, getTags } from "../shared"; +export type ClientOptions = openai.ClientOptions & { openpipe?: OpenPipeConfig }; export default class OpenAI extends openai.OpenAI { - public openPipeApi?: openPipeClient.DefaultApi; + public opClient?: OPClient; - constructor({ - openPipeApiKey = readEnv("OPENPIPE_API_KEY"), - openPipeBaseUrl = readEnv("OPENPIPE_BASE_URL") ?? `https://app.openpipe.ai/v1`, - ...opts - }: ClientOptions = {}) { - super({ ...opts }); + constructor({ openpipe, ...options }: ClientOptions = {}) { + super({ ...options }); + + const openPipeApiKey = openpipe?.apiKey ?? readEnv("OPENPIPE_API_KEY"); if (openPipeApiKey) { - const axiosInstance = axios.create({ - baseURL: openPipeBaseUrl, - headers: { - Authorization: `Bearer ${openPipeApiKey}`, - }, - }); - this.openPipeApi = new openPipeClient.DefaultApi( - new openPipeClient.Configuration({ - apiKey: openPipeApiKey, - basePath: openPipeBaseUrl, - }), - undefined, - axiosInstance + this.chat.setClient( + new OPClient({ + BASE: + openpipe?.baseUrl ?? readEnv("OPENPIPE_BASE_URL") ?? "https://app.openpipe.ai/api/v1", + TOKEN: openPipeApiKey, + }) ); - } - - // Override the chat property - this.chat = new ExtendedChat(this); - - if (openPipeApiKey === undefined) { - console.error( - "The OPENPIPE_API_KEY environment variable is missing or empty; either provide it, or instantiate the OpenPipe client with an openPipeApiKey option, like new OpenPipe({ openPipeApiKey: undefined })." + } else { + console.warn( + "You're using the OpenPipe client without an API key. No completion requests will be logged." ); } } + chat: WrappedChat = new WrappedChat(this); } -class ExtendedChat extends openai.OpenAI.Chat { - completions: ExtendedCompletions; - - constructor(openaiInstance: OpenAI) { - super(openaiInstance); - // Initialize the new completions instance - this.completions = new ExtendedCompletions(openaiInstance); +class WrappedChat extends openai.OpenAI.Chat { + setClient(client: OPClient) { + this.completions.opClient = client; } + + completions: InstrumentedCompletions = new InstrumentedCompletions(this.client); } -class ExtendedCompletions extends openai.OpenAI.Chat.Completions { - private openaiInstance: OpenAI; +class InstrumentedCompletions extends openai.OpenAI.Chat.Completions { + opClient?: OPClient; - constructor(openaiInstance: OpenAI) { - super(openaiInstance); - this.openaiInstance = openaiInstance; + constructor(client: openai.OpenAI, opClient?: OPClient) { + super(client); + this.opClient = opClient; } + _report(args: Parameters[0]) { + try { + return this.opClient ? this.opClient.default.report(args) : Promise.resolve(); + } catch (e) { + console.error(e); + return Promise.resolve(); + } + } + + create( + body: CompletionCreateParams.CreateChatCompletionRequestNonStreaming & OpenPipeArgs, + options?: Core.RequestOptions + ): Promise>; + create( + body: CompletionCreateParams.CreateChatCompletionRequestStreaming & OpenPipeArgs, + options?: Core.RequestOptions + ): Promise>>; async create( - params: - | CompletionCreateParams.CreateChatCompletionRequestNonStreaming - | CompletionCreateParams.CreateChatCompletionRequestStreaming, - options?: RequestOptions, - tags?: Record - ): Promise { - // // Your pre API call logic here - // console.log("Doing pre API call..."); + { openpipe, ...body }: CompletionCreateParams & OpenPipeArgs, + options?: Core.RequestOptions + ): Promise< + Core.APIResponse<(ChatCompletion & { openpipe: OpenPipeMeta }) | Stream> + > { + console.log("LALALA REPORT", this.opClient); + const requestedAt = Date.now(); + const cacheRequested = openpipe?.cache ?? false; - // // Determine the type of request - // if (params.hasOwnProperty("stream") && params.stream === true) { - // const result = await super.create( - // params as CompletionCreateParams.CreateChatCompletionRequestStreaming, - // options - // ); - // // Your post API call logic here - // console.log("Doing post API call for Streaming..."); - // return result; - // } else { - // const requestedAt = Date.now(); - const result = await super.create( - params as CompletionCreateParams.CreateChatCompletionRequestNonStreaming, - options - ); - return result; - // await this.openaiInstance.openPipeApi?.externalApiReport({ - // requestedAt, - // receivedAt: Date.now(), - // reqPayload: params, - // respPayload: result, - // statusCode: 200, - // errorMessage: undefined, - // tags, - // }); + if (cacheRequested) { + try { + const cached = await this.opClient?.default + .checkCache({ + requestedAt, + reqPayload: body, + tags: getTags(openpipe), + }) + .then((res) => res.respPayload); - // console.log("GOT RESULT", result); - // return result; - // } + if (cached) { + return { + ...cached, + openpipe: { + cacheStatus: "HIT", + reportingFinished: Promise.resolve(), + }, + }; + } + } catch (e) { + console.error(e); + } + } + + let reportingFinished: OpenPipeMeta["reportingFinished"] = Promise.resolve(); + + try { + if (body.stream) { + const stream = await super.create(body, options); + + // Do some logging of each chunk here + + return stream; + } else { + const response = await super.create(body, options); + + reportingFinished = this._report({ + requestedAt, + receivedAt: Date.now(), + reqPayload: body, + respPayload: response, + statusCode: 200, + tags: getTags(openpipe), + }); + return { + ...response, + openpipe: { + cacheStatus: cacheRequested ? "MISS" : "SKIP", + reportingFinished, + }, + }; + } + } catch (error: unknown) { + if (error instanceof openai.APIError) { + const rawMessage = error.message as string | string[]; + const message = Array.isArray(rawMessage) ? rawMessage.join(", ") : rawMessage; + reportingFinished = this._report({ + requestedAt, + receivedAt: Date.now(), + reqPayload: body, + respPayload: error.error, + statusCode: error.status, + errorMessage: message, + tags: getTags(openpipe), + }); + } + + throw error; + } } } diff --git a/client-libs/typescript/src/openai/mergeChunks.ts b/client-libs/typescript/src/openai/mergeChunks.ts new file mode 100644 index 0000000..2876a7c --- /dev/null +++ b/client-libs/typescript/src/openai/mergeChunks.ts @@ -0,0 +1,42 @@ +import { ChatCompletion, ChatCompletionChunk } from "openai-beta/resources/chat"; + +export default function mergeChunks( + base: ChatCompletion | null, + chunk: ChatCompletionChunk +): ChatCompletion { + if (base === null) { + return mergeChunks({ ...chunk, choices: [] }, chunk); + } + + const choices = [...base.choices]; + for (const choice of chunk.choices) { + const baseChoice = choices.find((c) => c.index === choice.index); + if (baseChoice) { + baseChoice.finish_reason = choice.finish_reason ?? baseChoice.finish_reason; + baseChoice.message = baseChoice.message ?? { role: "assistant" }; + + if (choice.delta?.content) + baseChoice.message.content = + ((baseChoice.message.content as string) ?? "") + (choice.delta.content ?? ""); + if (choice.delta?.function_call) { + const fnCall = baseChoice.message.function_call ?? {}; + fnCall.name = + ((fnCall.name as string) ?? "") + ((choice.delta.function_call.name as string) ?? ""); + fnCall.arguments = + ((fnCall.arguments as string) ?? "") + + ((choice.delta.function_call.arguments as string) ?? ""); + } + } else { + // @ts-expect-error the types are correctly telling us that finish_reason + // could be null, but don't want to fix it right now. + choices.push({ ...omit(choice, "delta"), message: { role: "assistant", ...choice.delta } }); + } + } + + const merged: ChatCompletion = { + ...base, + choices, + }; + + return merged; +} diff --git a/client-libs/typescript/src/shared.ts b/client-libs/typescript/src/shared.ts new file mode 100644 index 0000000..22110f0 --- /dev/null +++ b/client-libs/typescript/src/shared.ts @@ -0,0 +1,26 @@ +import pkg from "../package.json"; + +export type OpenPipeConfig = { + apiKey?: string; + baseUrl?: string; +}; + +export type OpenPipeArgs = { + openpipe?: { cache?: boolean; tags?: Record }; +}; + +export type OpenPipeMeta = { + cacheStatus: "HIT" | "MISS" | "SKIP"; + + // We report your call to OpenPipe asynchronously in the background. If you + // need to wait until the report is sent to take further action, you can await + // this promise. + reportingFinished: Promise; +}; + +export const getTags = (args: OpenPipeArgs["openpipe"]): Record => ({ + ...args?.tags, + ...(args?.cache ? { $cache: args.cache?.toString() } : {}), + $sdk: "typescript", + "$sdk.version": pkg.version, +}); diff --git a/client-libs/typescript/tsconfig.json b/client-libs/typescript/tsconfig.json index 9e89874..82e2400 100644 --- a/client-libs/typescript/tsconfig.json +++ b/client-libs/typescript/tsconfig.json @@ -15,10 +15,7 @@ "incremental": true, "noUncheckedIndexedAccess": true, "baseUrl": ".", - "outDir": "dist", - "paths": { - "~/*": ["./src/*"] - } + "outDir": "dist" }, "include": ["src/**/*.ts"], "exclude": ["node_modules"] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d7334b..3094949 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -321,6 +321,9 @@ importers: openapi-typescript: specifier: ^6.3.4 version: 6.3.4 + openapi-typescript-codegen: + specifier: ^0.25.0 + version: 0.25.0 prisma: specifier: ^4.14.0 version: 4.14.0 @@ -339,9 +342,12 @@ importers: client-libs/typescript: dependencies: - axios: - specifier: ^0.26.0 - version: 0.26.0 + form-data: + specifier: ^4.0.0 + version: 4.0.0 + node-fetch: + specifier: ^3.3.2 + version: 3.3.2 openai-beta: specifier: npm:openai@4.0.0-beta.7 version: /openai@4.0.0-beta.7 @@ -361,6 +367,9 @@ importers: typescript: specifier: ^5.0.4 version: 5.0.4 + vitest: + specifier: ^0.33.0 + version: 0.33.0 packages: @@ -402,6 +411,15 @@ packages: lodash.clonedeep: 4.5.0 dev: false + /@apidevtools/json-schema-ref-parser@9.0.9: + resolution: {integrity: sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==} + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.12 + call-me-maybe: 1.0.2 + js-yaml: 4.1.0 + dev: true + /@babel/code-frame@7.22.10: resolution: {integrity: sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==} engines: {node: '>=6.9.0'} @@ -2398,7 +2416,6 @@ packages: /@jsdevtools/ono@7.1.3: resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} - dev: false /@monaco-editor/loader@1.3.3(monaco-editor@0.40.0): resolution: {integrity: sha512-6KKF4CTzcJiS8BJwtxtfyYt9shBiEv32ateQ9T4UVogwn4HM/uPo9iJd2Dmbkpz8CM6Y0PDUpjnZzCwC+eYo2Q==} @@ -2958,7 +2975,7 @@ packages: /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 18.16.0 + '@types/node': 20.4.10 dev: true /@types/cookie@0.4.1: @@ -3062,7 +3079,7 @@ packages: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 18.16.0 + '@types/node': 20.4.10 dev: false /@types/hast@2.3.5: @@ -3122,7 +3139,7 @@ packages: /@types/node-fetch@2.6.4: resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} dependencies: - '@types/node': 18.16.0 + '@types/node': 20.4.10 form-data: 3.0.1 dev: false @@ -3206,7 +3223,7 @@ packages: resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} dependencies: '@types/mime': 1.3.2 - '@types/node': 18.16.0 + '@types/node': 20.4.10 dev: true /@types/serve-static@1.15.2: @@ -3933,12 +3950,16 @@ packages: /call-me-maybe@1.0.2: resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} - dev: false /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + /camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + dev: true + /camelize@1.0.1: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} dev: false @@ -4116,6 +4137,11 @@ packages: resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} dev: false + /commander@11.0.0: + resolution: {integrity: sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==} + engines: {node: '>=16'} + dev: true + /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -4379,6 +4405,11 @@ packages: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} dev: true + /data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + dev: false + /date-fns@2.30.0: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} @@ -4614,7 +4645,7 @@ packages: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.13 - '@types/node': 18.16.0 + '@types/node': 20.4.10 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 @@ -5250,6 +5281,14 @@ packages: format: 0.2.2 dev: false + /fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.2.1 + dev: false + /fflate@0.4.8: resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==} dev: false @@ -5367,6 +5406,13 @@ packages: web-streams-polyfill: 4.0.0-beta.3 dev: false + /formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + dependencies: + fetch-blob: 3.2.0 + dev: false + /forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -5401,6 +5447,15 @@ packages: engines: {node: '>= 0.6'} dev: false + /fs-extra@11.1.1: + resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==} + engines: {node: '>=14.14'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.0 + dev: true + /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -5620,6 +5675,19 @@ packages: uncrypto: 0.1.3 dev: false + /handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.17.4 + dev: true + /has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} dev: true @@ -6014,7 +6082,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.4.10 + '@types/node': 18.16.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -6049,6 +6117,14 @@ packages: /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + /json-schema-ref-parser@9.0.9: + resolution: {integrity: sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==} + engines: {node: '>=10'} + deprecated: Please switch to @apidevtools/json-schema-ref-parser + dependencies: + '@apidevtools/json-schema-ref-parser': 9.0.9 + dev: true + /json-schema-to-typescript@13.0.2: resolution: {integrity: sha512-TCaEVW4aI2FmMQe7f98mvr3/oiVmXEC1xZjkTZ9L/BSoTXFlC7p64mD5AD2d8XWycNBQZUnHwXL5iVXt1HWwNQ==} engines: {node: '>=12.0.0'} @@ -6097,6 +6173,14 @@ packages: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} dev: true + /jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + dependencies: + universalify: 2.0.0 + optionalDependencies: + graceful-fs: 4.2.11 + dev: true + /jsonschema@1.4.1: resolution: {integrity: sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==} dev: false @@ -6558,6 +6642,15 @@ packages: whatwg-url: 5.0.0 dev: false + /node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + dev: false + /node-mocks-http@1.12.2: resolution: {integrity: sha512-xhWwC0dh35R9rf0j3bRZXuISXdHxxtMx0ywZQBwjrg3yl7KpRETzogfeCamUIjltpn0Fxvs/ZhGJul1vPLrdJQ==} engines: {node: '>=0.6'} @@ -6715,6 +6808,17 @@ packages: resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} dev: false + /openapi-typescript-codegen@0.25.0: + resolution: {integrity: sha512-nN/TnIcGbP58qYgwEEy5FrAAjePcYgfMaCe3tsmYyTgI3v4RR9v8os14L+LEWDvV50+CmqiyTzRkKKtJeb6Ybg==} + hasBin: true + dependencies: + camelcase: 6.3.0 + commander: 11.0.0 + fs-extra: 11.1.1 + handlebars: 4.7.8 + json-schema-ref-parser: 9.0.9 + dev: true + /openapi-typescript@5.4.1: resolution: {integrity: sha512-AGB2QiZPz4rE7zIwV3dRHtoUC/CWHhUjuzGXvtmMQN2AFV8xCTLKcZUHLcdPQmt/83i22nRE7+TxXOXkK+gf4Q==} engines: {node: '>= 14.0.0'} @@ -8355,6 +8459,14 @@ packages: /ufo@1.2.0: resolution: {integrity: sha512-RsPyTbqORDNDxqAdQPQBpgqhWle1VcTSou/FraClYlHf6TZnQcGslpLcAphNR+sQW4q5lLWLbOsRlh9j24baQg==} + /uglify-js@3.17.4: + resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} + engines: {node: '>=0.8.0'} + hasBin: true + requiresBuild: true + dev: true + optional: true + /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: @@ -8382,6 +8494,11 @@ packages: tiny-inflate: 1.0.3 dev: false + /universalify@2.0.0: + resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} + engines: {node: '>= 10.0.0'} + dev: true + /unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -8547,7 +8664,7 @@ packages: d3-timer: 3.0.1 dev: false - /vite-node@0.33.0(@types/node@18.16.0): + /vite-node@0.33.0(@types/node@20.4.10): resolution: {integrity: sha512-19FpHYbwWWxDr73ruNahC+vtEdza52kA90Qb3La98yZ0xULqV8A5JLNPUff0f5zID4984tW7l3DH2przTJUZSw==} engines: {node: '>=v14.18.0'} hasBin: true @@ -8557,7 +8674,7 @@ packages: mlly: 1.4.0 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.4.9(@types/node@18.16.0) + vite: 4.4.9(@types/node@20.4.10) transitivePeerDependencies: - '@types/node' - less @@ -8585,7 +8702,7 @@ packages: - typescript dev: false - /vite@4.4.9(@types/node@18.16.0): + /vite@4.4.9(@types/node@20.4.10): resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -8613,7 +8730,7 @@ packages: terser: optional: true dependencies: - '@types/node': 18.16.0 + '@types/node': 20.4.10 esbuild: 0.18.20 postcss: 8.4.27 rollup: 3.28.0 @@ -8654,7 +8771,7 @@ packages: dependencies: '@types/chai': 4.3.5 '@types/chai-subset': 1.3.3 - '@types/node': 18.16.0 + '@types/node': 20.4.10 '@vitest/expect': 0.33.0 '@vitest/runner': 0.33.0 '@vitest/snapshot': 0.33.0 @@ -8673,8 +8790,8 @@ packages: strip-literal: 1.3.0 tinybench: 2.5.0 tinypool: 0.6.0 - vite: 4.4.9(@types/node@18.16.0) - vite-node: 0.33.0(@types/node@18.16.0) + vite: 4.4.9(@types/node@20.4.10) + vite-node: 0.33.0(@types/node@20.4.10) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -8693,6 +8810,11 @@ packages: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 + /web-streams-polyfill@3.2.1: + resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} + engines: {node: '>= 8'} + dev: false + /web-streams-polyfill@4.0.0-beta.3: resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} engines: {node: '>= 14'} @@ -8788,6 +8910,10 @@ packages: stackback: 0.0.2 dev: true + /wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + dev: true + /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'}