diff --git a/.gitattributes b/.gitattributes index 839c730..3d4500a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ .devcontainer/** linguist-vendored=true +src/types/generated/** linguist-generated=true diff --git a/CHANGELOG.md b/CHANGELOG.md index dc25734..bd93de9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ All notable changes to this project will be documented in this file. +## [0.4.0-rc.1](https://github.com/inference-gateway/typescript-sdk/compare/v0.3.5-rc.1...v0.4.0-rc.1) (2025-03-31) + +### ✨ Features + +* Add reasoning_content field to chunk message in OpenAPI specification ([4de08ed](https://github.com/inference-gateway/typescript-sdk/commit/4de08ed46f6078f77838bd9c4bae5e46eb12476c)) + +## [0.3.5-rc.1](https://github.com/inference-gateway/typescript-sdk/compare/v0.3.4...v0.3.5-rc.1) (2025-03-31) + +### ♻️ Improvements + +* Update type exports and add type generation task ([919679e](https://github.com/inference-gateway/typescript-sdk/commit/919679eac8142e25b5abcefd63ae00bc187f2a67)) + +### 🐛 Bug Fixes + +* Correct regex pattern for release candidate branches in configuration ([33db013](https://github.com/inference-gateway/typescript-sdk/commit/33db013392c8a1a15cc5a3bebb0f4c6d58a73d41)) +* Update release configuration to correctly match release candidate branches ([03d91e1](https://github.com/inference-gateway/typescript-sdk/commit/03d91e1d94d1fc11e50a535ba131ef2ca089653e)) + +### 🔧 Miscellaneous + +* Remove unnecessary line from .gitattributes ([66407b4](https://github.com/inference-gateway/typescript-sdk/commit/66407b4cba0bf96af457dbb66818f48da3a4abda)) +* Update .gitattributes to mark generated types as linguist-generated ([67f3d68](https://github.com/inference-gateway/typescript-sdk/commit/67f3d682ba1e131f9e416c45e097c76dfeec4bf6)) + ## [0.3.4](https://github.com/inference-gateway/typescript-sdk/compare/v0.3.3...v0.3.4) (2025-03-31) ### ♻️ Improvements diff --git a/Taskfile.yml b/Taskfile.yml index 928b1b8..9fbe27a 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,5 +1,5 @@ --- -version: "3" +version: '3' tasks: oas-download: @@ -21,3 +21,9 @@ tasks: desc: Run tests cmds: - npm run test + + generate-types: + desc: Generate TypeScript types from OpenAPI specification + cmds: + - npx openapi-typescript openapi.yaml --enum --enum-values --dedupe-enums=true --root-types -o src/types/generated/index.ts + - npx prettier --write src/types/generated/index.ts diff --git a/openapi.yaml b/openapi.yaml index f2177da..e15c6a2 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -904,6 +904,9 @@ components: content: type: string description: The contents of the chunk message. + reasoning_content: + type: string + description: The reasoning content of the chunk message. tool_calls: type: array items: diff --git a/package.json b/package.json index b01d965..0ff3fbf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@inference-gateway/sdk", - "version": "0.3.4", + "version": "0.4.0-rc.1", "description": "An SDK written in Typescript for the [Inference Gateway](https://github.com/inference-gateway/inference-gateway).", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", diff --git a/src/client.ts b/src/client.ts index 26bf294..f588e55 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,13 +1,24 @@ -import { - Error as ApiError, - ChatCompletionMessageToolCall, - ChatCompletionRequest, - ChatCompletionResponse, - ChatCompletionStreamCallbacks, - ChatCompletionStreamResponse, - ListModelsResponse, +import type { Provider, -} from './types'; + SchemaChatCompletionMessageToolCall, + SchemaCreateChatCompletionRequest, + SchemaCreateChatCompletionResponse, + SchemaCreateChatCompletionStreamResponse, + SchemaError, + SchemaListModelsResponse, +} from './types/generated'; +import { ChatCompletionToolType } from './types/generated'; + +interface ChatCompletionStreamCallbacks { + onOpen?: () => void; + onChunk?: (chunk: SchemaCreateChatCompletionStreamResponse) => void; + onContent?: (content: string) => void; + onTool?: (toolCall: SchemaChatCompletionMessageToolCall) => void; + onFinish?: ( + response: SchemaCreateChatCompletionStreamResponse | null + ) => void; + onError?: (error: SchemaError) => void; +} export interface ClientOptions { baseURL?: string; @@ -90,7 +101,7 @@ export class InferenceGatewayClient { }); if (!response.ok) { - const error = (await response.json()) as ApiError; + const error: SchemaError = await response.json(); throw new Error( error.error || `HTTP error! status: ${response.status}` ); @@ -105,12 +116,12 @@ export class InferenceGatewayClient { /** * Lists the currently available models. */ - async listModels(provider?: Provider): Promise { + async listModels(provider?: Provider): Promise { const query: Record = {}; if (provider) { query.provider = provider; } - return this.request( + return this.request( '/models', { method: 'GET' }, query @@ -121,14 +132,14 @@ export class InferenceGatewayClient { * Creates a chat completion. */ async createChatCompletion( - request: ChatCompletionRequest, + request: SchemaCreateChatCompletionRequest, provider?: Provider - ): Promise { + ): Promise { const query: Record = {}; if (provider) { query.provider = provider; } - return this.request( + return this.request( '/chat/completions', { method: 'POST', @@ -142,7 +153,7 @@ export class InferenceGatewayClient { * Creates a streaming chat completion. */ async streamChatCompletion( - request: ChatCompletionRequest, + request: SchemaCreateChatCompletionRequest, callbacks: ChatCompletionStreamCallbacks, provider?: Provider ): Promise { @@ -186,7 +197,7 @@ export class InferenceGatewayClient { }); if (!response.ok) { - const error = (await response.json()) as ApiError; + const error: SchemaError = await response.json(); throw new Error( error.error || `HTTP error! status: ${response.status}` ); @@ -215,14 +226,13 @@ export class InferenceGatewayClient { const data = line.slice(5).trim(); if (data === '[DONE]') { - callbacks.onFinish?.( - null as unknown as ChatCompletionStreamResponse - ); + callbacks.onFinish?.(null); return; } try { - const chunk = JSON.parse(data) as ChatCompletionStreamResponse; + const chunk: SchemaCreateChatCompletionStreamResponse = + JSON.parse(data); callbacks.onChunk?.(chunk); const content = chunk.choices[0]?.delta?.content; @@ -232,9 +242,9 @@ export class InferenceGatewayClient { const toolCalls = chunk.choices[0]?.delta?.tool_calls; if (toolCalls && toolCalls.length > 0) { - const toolCall: ChatCompletionMessageToolCall = { + const toolCall: SchemaChatCompletionMessageToolCall = { id: toolCalls[0].id || '', - type: 'function', + type: ChatCompletionToolType.function, function: { name: toolCalls[0].function?.name || '', arguments: toolCalls[0].function?.arguments || '', @@ -249,7 +259,7 @@ export class InferenceGatewayClient { } } } catch (error) { - const apiError: ApiError = { + const apiError: SchemaError = { error: (error as Error).message || 'Unknown error', }; callbacks.onError?.(apiError); diff --git a/src/index.ts b/src/index.ts index d860d06..687c3f2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,2 @@ export * from './client'; -export * from './types'; +export * from './types/generated'; diff --git a/src/types/generated/index.ts b/src/types/generated/index.ts new file mode 100644 index 0000000..51ca143 --- /dev/null +++ b/src/types/generated/index.ts @@ -0,0 +1,763 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + '/models': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Lists the currently available models, and provides basic information about each one such as the owner and availability. + * @description Lists the currently available models, and provides basic information + * about each one such as the owner and availability. + * + */ + get: operations['listModels']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/chat/completions': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create a chat completion + * @description Generates a chat completion based on the provided input. + * The completion can be streamed to the client as it is generated. + * + */ + post: operations['createChatCompletion']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/proxy/{provider}/{path}': { + parameters: { + query?: never; + header?: never; + path: { + provider: components['schemas']['Provider']; + /** @description The remaining path to proxy to the provider */ + path: string; + }; + cookie?: never; + }; + /** + * Proxy GET request to provider + * @description Proxy GET request to provider + * The request body depends on the specific provider and endpoint being called. + * If you decide to use this approach, please follow the provider-specific documentations. + * + */ + get: operations['proxyGet']; + /** + * Proxy PUT request to provider + * @description Proxy PUT request to provider + * The request body depends on the specific provider and endpoint being called. + * If you decide to use this approach, please follow the provider-specific documentations. + * + */ + put: operations['proxyPut']; + /** + * Proxy POST request to provider + * @description Proxy POST request to provider + * The request body depends on the specific provider and endpoint being called. + * If you decide to use this approach, please follow the provider-specific documentations. + * + */ + post: operations['proxyPost']; + /** + * Proxy DELETE request to provider + * @description Proxy DELETE request to provider + * The request body depends on the specific provider and endpoint being called. + * If you decide to use this approach, please follow the provider-specific documentations. + * + */ + delete: operations['proxyDelete']; + options?: never; + head?: never; + /** + * Proxy PATCH request to provider + * @description Proxy PATCH request to provider + * The request body depends on the specific provider and endpoint being called. + * If you decide to use this approach, please follow the provider-specific documentations. + * + */ + patch: operations['proxyPatch']; + trace?: never; + }; + '/health': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Health check + * @description Health check endpoint + * Returns a 200 status code if the service is healthy + * + */ + get: operations['healthCheck']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + /** @enum {string} */ + Provider: Provider; + /** @description Provider-specific response format. Examples: + * + * OpenAI GET /v1/models?provider=openai response: + * ```json + * { + * "provider": "openai", + * "object": "list", + * "data": [ + * { + * "id": "gpt-4", + * "object": "model", + * "created": 1687882410, + * "owned_by": "openai", + * "served_by": "openai" + * } + * ] + * } + * ``` + * + * Anthropic GET /v1/models?provider=anthropic response: + * ```json + * { + * "provider": "anthropic", + * "object": "list", + * "data": [ + * { + * "id": "gpt-4", + * "object": "model", + * "created": 1687882410, + * "owned_by": "openai", + * "served_by": "openai" + * } + * ] + * } + * ``` + * */ + ProviderSpecificResponse: Record; + /** + * @description Authentication type for providers + * @enum {string} + */ + ProviderAuthType: ProviderAuthType; + SSEvent: { + /** @enum {string} */ + event?: SSEventEvent; + /** Format: byte */ + data?: string; + retry?: number; + }; + Endpoints: { + models?: string; + chat?: string; + }; + Error: { + error?: string; + }; + /** + * @description Role of the message sender + * @enum {string} + */ + MessageRole: MessageRole; + /** @description Message structure for provider requests */ + Message: { + role: components['schemas']['MessageRole']; + content: string; + tool_calls?: components['schemas']['ChatCompletionMessageToolCall'][]; + tool_call_id?: string; + reasoning?: string; + reasoning_content?: string; + }; + /** @description Common model information */ + Model: { + id?: string; + object?: string; + /** Format: int64 */ + created?: number; + owned_by?: string; + served_by?: components['schemas']['Provider']; + }; + /** @description Response structure for listing models */ + ListModelsResponse: { + provider?: components['schemas']['Provider']; + object?: string; + /** @default [] */ + data: components['schemas']['Model'][]; + }; + FunctionObject: { + /** @description A description of what the function does, used by the model to choose when and how to call the function. */ + description?: string; + /** @description The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64. */ + name: string; + parameters?: components['schemas']['FunctionParameters']; + /** + * @description Whether to enable strict schema adherence when generating the function call. If set to true, the model will follow the exact schema defined in the `parameters` field. Only a subset of JSON Schema is supported when `strict` is `true`. Learn more about Structured Outputs in the [function calling guide](docs/guides/function-calling). + * @default false + */ + strict: boolean; + }; + ChatCompletionTool: { + type: components['schemas']['ChatCompletionToolType']; + function: components['schemas']['FunctionObject']; + }; + /** @description The parameters the functions accepts, described as a JSON Schema object. See the [guide](/docs/guides/function-calling) for examples, and the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format. + * Omitting `parameters` defines a function with an empty parameter list. */ + FunctionParameters: { + /** @description The type of the parameters. Currently, only `object` is supported. */ + type?: string; + /** @description The properties of the parameters. */ + properties?: Record; + /** @description The required properties of the parameters. */ + required?: string[]; + }; + /** + * @description The type of the tool. Currently, only `function` is supported. + * @enum {string} + */ + ChatCompletionToolType: ChatCompletionToolType; + /** @description Usage statistics for the completion request. */ + CompletionUsage: { + /** + * Format: int64 + * @description Number of tokens in the generated completion. + * @default 0 + */ + completion_tokens: number; + /** + * Format: int64 + * @description Number of tokens in the prompt. + * @default 0 + */ + prompt_tokens: number; + /** + * Format: int64 + * @description Total number of tokens used in the request (prompt + completion). + * @default 0 + */ + total_tokens: number; + }; + /** @description Options for streaming response. Only set this when you set `stream: true`. + * */ + ChatCompletionStreamOptions: { + /** + * @description If set, an additional chunk will be streamed before the `data: [DONE]` message. The `usage` field on this chunk shows the token usage statistics for the entire request, and the `choices` field will always be an empty array. All other chunks will also include a `usage` field, but with a null value. + * + * @default true + */ + include_usage: boolean; + }; + CreateChatCompletionRequest: { + /** @description Model ID to use */ + model: string; + /** @description A list of messages comprising the conversation so far. + * */ + messages: components['schemas']['Message'][]; + /** @description An upper bound for the number of tokens that can be generated for a completion, including visible output tokens and reasoning tokens. + * */ + max_tokens?: number; + /** + * @description If set to true, the model response data will be streamed to the client as it is generated using [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format). + * + * @default false + */ + stream: boolean; + stream_options?: components['schemas']['ChatCompletionStreamOptions']; + /** @description A list of tools the model may call. Currently, only functions are supported as a tool. Use this to provide a list of functions the model may generate JSON inputs for. A max of 128 functions are supported. + * */ + tools?: components['schemas']['ChatCompletionTool'][]; + }; + /** @description The function that the model called. */ + ChatCompletionMessageToolCallFunction: { + /** @description The name of the function to call. */ + name: string; + /** @description The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function. */ + arguments: string; + }; + ChatCompletionMessageToolCall: { + /** @description The ID of the tool call. */ + id: string; + type: components['schemas']['ChatCompletionToolType']; + function: components['schemas']['ChatCompletionMessageToolCallFunction']; + }; + ChatCompletionChoice: { + /** + * @description The reason the model stopped generating tokens. This will be `stop` if the model hit a natural stop point or a provided stop sequence, + * `length` if the maximum number of tokens specified in the request was reached, + * `content_filter` if content was omitted due to a flag from our content filters, + * `tool_calls` if the model called a tool. + * + * @enum {string} + */ + finish_reason: ChatCompletionChoiceFinish_reason; + /** @description The index of the choice in the list of choices. */ + index: number; + message: components['schemas']['Message']; + }; + ChatCompletionStreamChoice: { + delta: components['schemas']['ChatCompletionStreamResponseDelta']; + /** @description Log probability information for the choice. */ + logprobs?: { + /** @description A list of message content tokens with log probability information. */ + content: components['schemas']['ChatCompletionTokenLogprob'][]; + /** @description A list of message refusal tokens with log probability information. */ + refusal: components['schemas']['ChatCompletionTokenLogprob'][]; + }; + finish_reason: components['schemas']['FinishReason']; + /** @description The index of the choice in the list of choices. */ + index: number; + }; + /** @description Represents a chat completion response returned by model, based on the provided input. */ + CreateChatCompletionResponse: { + /** @description A unique identifier for the chat completion. */ + id: string; + /** @description A list of chat completion choices. Can be more than one if `n` is greater than 1. */ + choices: components['schemas']['ChatCompletionChoice'][]; + /** @description The Unix timestamp (in seconds) of when the chat completion was created. */ + created: number; + /** @description The model used for the chat completion. */ + model: string; + /** @description The object type, which is always `chat.completion`. */ + object: string; + usage?: components['schemas']['CompletionUsage']; + }; + /** @description A chat completion delta generated by streamed model responses. */ + ChatCompletionStreamResponseDelta: { + /** @description The contents of the chunk message. */ + content?: string; + /** @description The reasoning content of the chunk message. */ + reasoning_content?: string; + tool_calls?: components['schemas']['ChatCompletionMessageToolCallChunk'][]; + role?: components['schemas']['MessageRole']; + /** @description The refusal message generated by the model. */ + refusal?: string; + }; + ChatCompletionMessageToolCallChunk: { + index: number; + /** @description The ID of the tool call. */ + id?: string; + /** @description The type of the tool. Currently, only `function` is supported. */ + type?: string; + function?: { + /** @description The name of the function to call. */ + name?: string; + /** @description The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function. */ + arguments?: string; + }; + }; + ChatCompletionTokenLogprob: { + /** @description The token. */ + token: string; + /** @description The log probability of this token, if it is within the top 20 most likely tokens. Otherwise, the value `-9999.0` is used to signify that the token is very unlikely. */ + logprob: number; + /** @description A list of integers representing the UTF-8 bytes representation of the token. Useful in instances where characters are represented by multiple tokens and their byte representations must be combined to generate the correct text representation. Can be `null` if there is no bytes representation for the token. */ + bytes: number[]; + /** @description List of the most likely tokens and their log probability, at this token position. In rare cases, there may be fewer than the number of requested `top_logprobs` returned. */ + top_logprobs: { + /** @description The token. */ + token: string; + /** @description The log probability of this token, if it is within the top 20 most likely tokens. Otherwise, the value `-9999.0` is used to signify that the token is very unlikely. */ + logprob: number; + /** @description A list of integers representing the UTF-8 bytes representation of the token. Useful in instances where characters are represented by multiple tokens and their byte representations must be combined to generate the correct text representation. Can be `null` if there is no bytes representation for the token. */ + bytes: number[]; + }[]; + }; + /** + * @description The reason the model stopped generating tokens. This will be `stop` if the model hit a natural stop point or a provided stop sequence, + * `length` if the maximum number of tokens specified in the request was reached, + * `content_filter` if content was omitted due to a flag from our content filters, + * `tool_calls` if the model called a tool. + * + * @enum {string} + */ + FinishReason: ChatCompletionChoiceFinish_reason; + /** @description Represents a streamed chunk of a chat completion response returned + * by the model, based on the provided input. + * */ + CreateChatCompletionStreamResponse: { + /** @description A unique identifier for the chat completion. Each chunk has the same ID. */ + id: string; + /** @description A list of chat completion choices. Can contain more than one elements if `n` is greater than 1. Can also be empty for the + * last chunk if you set `stream_options: {"include_usage": true}`. + * */ + choices: components['schemas']['ChatCompletionStreamChoice'][]; + /** @description The Unix timestamp (in seconds) of when the chat completion was created. Each chunk has the same timestamp. */ + created: number; + /** @description The model to generate the completion. */ + model: string; + /** @description This fingerprint represents the backend configuration that the model runs with. + * Can be used in conjunction with the `seed` request parameter to understand when backend changes have been made that might impact determinism. + * */ + system_fingerprint?: string; + /** @description The object type, which is always `chat.completion.chunk`. */ + object: string; + usage?: components['schemas']['CompletionUsage']; + }; + Config: unknown; + }; + responses: { + /** @description Bad request */ + BadRequest: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Unauthorized */ + Unauthorized: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + InternalError: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description ProviderResponse depends on the specific provider and endpoint being called + * If you decide to use this approach, please follow the provider-specific documentations. + * */ + ProviderResponse: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ProviderSpecificResponse']; + }; + }; + }; + parameters: never; + requestBodies: { + /** @description ProviderRequest depends on the specific provider and endpoint being called + * If you decide to use this approach, please follow the provider-specific documentations. + * */ + ProviderRequest: { + content: { + 'application/json': { + model?: string; + messages?: { + role?: string; + content?: string; + }[]; + /** + * Format: float + * @default 0.7 + */ + temperature?: number; + }; + }; + }; + /** @description ProviderRequest depends on the specific provider and endpoint being called + * If you decide to use this approach, please follow the provider-specific documentations. + * */ + CreateChatCompletionRequest: { + content: { + 'application/json': components['schemas']['CreateChatCompletionRequest']; + }; + }; + }; + headers: never; + pathItems: never; +} +export type SchemaProvider = components['schemas']['Provider']; +export type SchemaProviderSpecificResponse = + components['schemas']['ProviderSpecificResponse']; +export type SchemaProviderAuthType = components['schemas']['ProviderAuthType']; +export type SchemaSsEvent = components['schemas']['SSEvent']; +export type SchemaEndpoints = components['schemas']['Endpoints']; +export type SchemaError = components['schemas']['Error']; +export type SchemaMessageRole = components['schemas']['MessageRole']; +export type SchemaMessage = components['schemas']['Message']; +export type SchemaModel = components['schemas']['Model']; +export type SchemaListModelsResponse = + components['schemas']['ListModelsResponse']; +export type SchemaFunctionObject = components['schemas']['FunctionObject']; +export type SchemaChatCompletionTool = + components['schemas']['ChatCompletionTool']; +export type SchemaFunctionParameters = + components['schemas']['FunctionParameters']; +export type SchemaChatCompletionToolType = + components['schemas']['ChatCompletionToolType']; +export type SchemaCompletionUsage = components['schemas']['CompletionUsage']; +export type SchemaChatCompletionStreamOptions = + components['schemas']['ChatCompletionStreamOptions']; +export type SchemaCreateChatCompletionRequest = + components['schemas']['CreateChatCompletionRequest']; +export type SchemaChatCompletionMessageToolCallFunction = + components['schemas']['ChatCompletionMessageToolCallFunction']; +export type SchemaChatCompletionMessageToolCall = + components['schemas']['ChatCompletionMessageToolCall']; +export type SchemaChatCompletionChoice = + components['schemas']['ChatCompletionChoice']; +export type SchemaChatCompletionStreamChoice = + components['schemas']['ChatCompletionStreamChoice']; +export type SchemaCreateChatCompletionResponse = + components['schemas']['CreateChatCompletionResponse']; +export type SchemaChatCompletionStreamResponseDelta = + components['schemas']['ChatCompletionStreamResponseDelta']; +export type SchemaChatCompletionMessageToolCallChunk = + components['schemas']['ChatCompletionMessageToolCallChunk']; +export type SchemaChatCompletionTokenLogprob = + components['schemas']['ChatCompletionTokenLogprob']; +export type SchemaFinishReason = components['schemas']['FinishReason']; +export type SchemaCreateChatCompletionStreamResponse = + components['schemas']['CreateChatCompletionStreamResponse']; +export type SchemaConfig = components['schemas']['Config']; +export type ResponseBadRequest = components['responses']['BadRequest']; +export type ResponseUnauthorized = components['responses']['Unauthorized']; +export type ResponseInternalError = components['responses']['InternalError']; +export type ResponseProviderResponse = + components['responses']['ProviderResponse']; +export type RequestBodyProviderRequest = + components['requestBodies']['ProviderRequest']; +export type RequestBodyCreateChatCompletionRequest = + components['requestBodies']['CreateChatCompletionRequest']; +export type $defs = Record; +export interface operations { + listModels: { + parameters: { + query?: { + /** @description Specific provider to query (optional) */ + provider?: components['schemas']['Provider']; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of available models */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ListModelsResponse']; + }; + }; + 401: components['responses']['Unauthorized']; + 500: components['responses']['InternalError']; + }; + }; + createChatCompletion: { + parameters: { + query?: { + /** @description Specific provider to use (default determined by model) */ + provider?: components['schemas']['Provider']; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: components['requestBodies']['CreateChatCompletionRequest']; + responses: { + /** @description Successful response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CreateChatCompletionResponse']; + 'text/event-stream': components['schemas']['SSEvent']; + }; + }; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthorized']; + 500: components['responses']['InternalError']; + }; + }; + proxyGet: { + parameters: { + query?: never; + header?: never; + path: { + provider: components['schemas']['Provider']; + /** @description The remaining path to proxy to the provider */ + path: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components['responses']['ProviderResponse']; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthorized']; + 500: components['responses']['InternalError']; + }; + }; + proxyPut: { + parameters: { + query?: never; + header?: never; + path: { + provider: components['schemas']['Provider']; + /** @description The remaining path to proxy to the provider */ + path: string; + }; + cookie?: never; + }; + requestBody: components['requestBodies']['ProviderRequest']; + responses: { + 200: components['responses']['ProviderResponse']; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthorized']; + 500: components['responses']['InternalError']; + }; + }; + proxyPost: { + parameters: { + query?: never; + header?: never; + path: { + provider: components['schemas']['Provider']; + /** @description The remaining path to proxy to the provider */ + path: string; + }; + cookie?: never; + }; + requestBody: components['requestBodies']['ProviderRequest']; + responses: { + 200: components['responses']['ProviderResponse']; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthorized']; + 500: components['responses']['InternalError']; + }; + }; + proxyDelete: { + parameters: { + query?: never; + header?: never; + path: { + provider: components['schemas']['Provider']; + /** @description The remaining path to proxy to the provider */ + path: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components['responses']['ProviderResponse']; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthorized']; + 500: components['responses']['InternalError']; + }; + }; + proxyPatch: { + parameters: { + query?: never; + header?: never; + path: { + provider: components['schemas']['Provider']; + /** @description The remaining path to proxy to the provider */ + path: string; + }; + cookie?: never; + }; + requestBody: components['requestBodies']['ProviderRequest']; + responses: { + 200: components['responses']['ProviderResponse']; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthorized']; + 500: components['responses']['InternalError']; + }; + }; + healthCheck: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Health check successful */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; +} +export enum Provider { + ollama = 'ollama', + groq = 'groq', + openai = 'openai', + cloudflare = 'cloudflare', + cohere = 'cohere', + anthropic = 'anthropic', + deepseek = 'deepseek', +} +export enum ProviderAuthType { + bearer = 'bearer', + xheader = 'xheader', + query = 'query', + none = 'none', +} +export enum SSEventEvent { + message_start = 'message-start', + stream_start = 'stream-start', + content_start = 'content-start', + content_delta = 'content-delta', + content_end = 'content-end', + message_end = 'message-end', + stream_end = 'stream-end', +} +export enum MessageRole { + system = 'system', + user = 'user', + assistant = 'assistant', + tool = 'tool', +} +export enum ChatCompletionToolType { + function = 'function', +} +export enum ChatCompletionChoiceFinish_reason { + stop = 'stop', + length = 'length', + tool_calls = 'tool_calls', + content_filter = 'content_filter', + function_call = 'function_call', +} diff --git a/src/types/index.ts b/src/types/index.ts deleted file mode 100644 index 22ee30a..0000000 --- a/src/types/index.ts +++ /dev/null @@ -1,161 +0,0 @@ -export enum Provider { - Ollama = 'ollama', - Groq = 'groq', - OpenAI = 'openai', - Cloudflare = 'cloudflare', - Cohere = 'cohere', - Anthropic = 'anthropic', - DeepSeek = 'deepseek', -} - -export enum MessageRole { - System = 'system', - User = 'user', - Assistant = 'assistant', - Tool = 'tool', -} - -export interface Message { - role: MessageRole; - content: string; - reasoning_content?: string; - reasoning?: string; - tool_calls?: ChatCompletionMessageToolCall[]; - tool_call_id?: string; -} - -export interface Model { - id: string; - object: string; - created: number; - owned_by: string; -} - -export interface ListModelsResponse { - object: string; - data: Model[]; -} - -export interface ChatCompletionMessageToolCallFunction { - name: string; - arguments: string; -} - -export interface ChatCompletionMessageToolCall { - id: string; - type: 'function'; - function: ChatCompletionMessageToolCallFunction; -} - -export interface ChatCompletionMessageToolCallChunk { - index: number; - id?: string; - type?: string; - function?: { - name?: string; - arguments?: string; - }; -} - -export interface FunctionParameters { - type: string; - properties?: Record; - required?: string[]; -} - -export interface FunctionObject { - description?: string; - name: string; - parameters: FunctionParameters; - strict?: boolean; -} - -export interface ChatCompletionTool { - type: 'function'; - function: FunctionObject; -} - -export interface ChatCompletionRequest { - model: string; - messages: Message[]; - max_tokens?: number; - stream?: boolean; - stream_options?: ChatCompletionStreamOptions; - tools?: ChatCompletionTool[]; - temperature?: number; - top_p?: number; - top_k?: number; -} - -export interface ChatCompletionStreamOptions { - include_usage?: boolean; -} - -export interface ChatCompletionChoice { - finish_reason: - | 'stop' - | 'length' - | 'tool_calls' - | 'content_filter' - | 'function_call'; - index: number; - message: Message; - logprobs?: Record; -} - -export interface CompletionUsage { - prompt_tokens: number; - completion_tokens: number; - total_tokens: number; -} - -export interface ChatCompletionResponse { - id: string; - choices: ChatCompletionChoice[]; - created: number; - model: string; - object: string; - usage?: CompletionUsage; -} - -export interface ChatCompletionStreamChoice { - delta: ChatCompletionStreamResponseDelta; - finish_reason: - | 'stop' - | 'length' - | 'tool_calls' - | 'content_filter' - | 'function_call' - | null; - index: number; - logprobs?: Record; -} - -export interface ChatCompletionStreamResponseDelta { - content?: string; - tool_calls?: ChatCompletionMessageToolCallChunk[]; - role?: MessageRole; - refusal?: string; -} - -export interface ChatCompletionStreamResponse { - id: string; - choices: ChatCompletionStreamChoice[]; - created: number; - model: string; - object: string; - usage?: CompletionUsage; -} - -export interface ChatCompletionStreamCallbacks { - onOpen?: () => void; - onChunk?: (chunk: ChatCompletionStreamResponse) => void; - onContent?: (content: string) => void; - onTool?: (toolCall: ChatCompletionMessageToolCall) => void; - onFinish?: (response: ChatCompletionStreamResponse) => void; - onError?: (error: Error) => void; -} - -export interface Error { - error: string; -} diff --git a/tests/client.test.ts b/tests/client.test.ts index 987e165..471b090 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -1,10 +1,14 @@ import { InferenceGatewayClient } from '@/client'; +import type { + SchemaCreateChatCompletionResponse, + SchemaListModelsResponse, +} from '@/types/generated'; import { - ChatCompletionResponse, - ListModelsResponse, + ChatCompletionChoiceFinish_reason, + ChatCompletionToolType, MessageRole, Provider, -} from '@/types'; +} from '@/types/generated'; import { TransformStream } from 'node:stream/web'; import { TextEncoder } from 'node:util'; @@ -25,7 +29,7 @@ describe('InferenceGatewayClient', () => { describe('listModels', () => { it('should fetch available models', async () => { - const mockResponse: ListModelsResponse = { + const mockResponse: SchemaListModelsResponse = { object: 'list', data: [ { @@ -33,12 +37,14 @@ describe('InferenceGatewayClient', () => { object: 'model', created: 1686935002, owned_by: 'openai', + served_by: Provider.openai, }, { id: 'llama-3.3-70b-versatile', object: 'model', created: 1723651281, owned_by: 'groq', + served_by: Provider.groq, }, ], }; @@ -60,7 +66,7 @@ describe('InferenceGatewayClient', () => { }); it('should fetch models for a specific provider', async () => { - const mockResponse: ListModelsResponse = { + const mockResponse: SchemaListModelsResponse = { object: 'list', data: [ { @@ -77,7 +83,7 @@ describe('InferenceGatewayClient', () => { json: () => Promise.resolve(mockResponse), }); - const result = await client.listModels(Provider.OpenAI); + const result = await client.listModels(Provider.openai); expect(result).toEqual(mockResponse); expect(mockFetch).toHaveBeenCalledWith( 'http://localhost:8080/v1/models?provider=openai', @@ -96,7 +102,7 @@ describe('InferenceGatewayClient', () => { json: () => Promise.resolve({ error: errorMessage }), }); - await expect(client.listModels(Provider.OpenAI)).rejects.toThrow( + await expect(client.listModels(Provider.openai)).rejects.toThrow( errorMessage ); }); @@ -107,12 +113,13 @@ describe('InferenceGatewayClient', () => { const mockRequest = { model: 'gpt-4o', messages: [ - { role: MessageRole.System, content: 'You are a helpful assistant' }, - { role: MessageRole.User, content: 'Hello' }, + { role: MessageRole.system, content: 'You are a helpful assistant' }, + { role: MessageRole.user, content: 'Hello' }, ], + stream: false, }; - const mockResponse: ChatCompletionResponse = { + const mockResponse: SchemaCreateChatCompletionResponse = { id: 'chatcmpl-123', object: 'chat.completion', created: 1677652288, @@ -121,10 +128,10 @@ describe('InferenceGatewayClient', () => { { index: 0, message: { - role: MessageRole.Assistant, + role: MessageRole.assistant, content: 'Hello! How can I help you today?', }, - finish_reason: 'stop', + finish_reason: ChatCompletionChoiceFinish_reason.stop, }, ], usage: { @@ -153,10 +160,11 @@ describe('InferenceGatewayClient', () => { it('should create a chat completion with a specific provider', async () => { const mockRequest = { model: 'claude-3-opus-20240229', - messages: [{ role: MessageRole.User, content: 'Hello' }], + messages: [{ role: MessageRole.user, content: 'Hello' }], + stream: false, }; - const mockResponse: ChatCompletionResponse = { + const mockResponse: SchemaCreateChatCompletionResponse = { id: 'chatcmpl-456', object: 'chat.completion', created: 1677652288, @@ -165,10 +173,10 @@ describe('InferenceGatewayClient', () => { { index: 0, message: { - role: MessageRole.Assistant, + role: MessageRole.assistant, content: 'Hello! How can I assist you today?', }, - finish_reason: 'stop', + finish_reason: ChatCompletionChoiceFinish_reason.stop, }, ], usage: { @@ -185,7 +193,7 @@ describe('InferenceGatewayClient', () => { const result = await client.createChatCompletion( mockRequest, - Provider.Anthropic + Provider.anthropic ); expect(result).toEqual(mockResponse); expect(mockFetch).toHaveBeenCalledWith( @@ -202,7 +210,8 @@ describe('InferenceGatewayClient', () => { it('should handle streaming chat completions', async () => { const mockRequest = { model: 'gpt-4o', - messages: [{ role: MessageRole.User, content: 'Hello' }], + messages: [{ role: MessageRole.user, content: 'Hello' }], + stream: true, }; const mockStream = new TransformStream(); @@ -224,7 +233,6 @@ describe('InferenceGatewayClient', () => { const streamPromise = client.streamChatCompletion(mockRequest, callbacks); - // Simulate SSE events await writer.write( encoder.encode( 'data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"gpt-4o","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}\n\n' + @@ -260,28 +268,20 @@ describe('InferenceGatewayClient', () => { model: 'gpt-4o', messages: [ { - role: MessageRole.User, + role: MessageRole.user, content: 'What is the weather in San Francisco?', }, ], tools: [ { - type: 'function' as const, + type: ChatCompletionToolType.function, function: { name: 'get_weather', - parameters: { - type: 'object', - properties: { - location: { - type: 'string', - description: 'The city and state, e.g. San Francisco, CA', - }, - }, - required: ['location'], - }, + strict: true, }, }, ], + stream: true, }; const mockStream = new TransformStream(); @@ -320,14 +320,15 @@ describe('InferenceGatewayClient', () => { expect(callbacks.onOpen).toHaveBeenCalledTimes(1); expect(callbacks.onChunk).toHaveBeenCalledTimes(6); - expect(callbacks.onTool).toHaveBeenCalledTimes(4); // Called for each chunk with tool_calls + expect(callbacks.onTool).toHaveBeenCalledTimes(4); expect(callbacks.onFinish).toHaveBeenCalledTimes(1); }); it('should handle errors in streaming chat completions', async () => { const mockRequest = { model: 'gpt-4o', - messages: [{ role: MessageRole.User, content: 'Hello' }], + messages: [{ role: MessageRole.user, content: 'Hello' }], + stream: true, }; mockFetch.mockResolvedValueOnce({ @@ -357,7 +358,7 @@ describe('InferenceGatewayClient', () => { json: () => Promise.resolve(mockResponse), }); - const result = await client.proxy(Provider.OpenAI, 'embeddings', { + const result = await client.proxy(Provider.openai, 'embeddings', { method: 'POST', body: JSON.stringify({ model: 'text-embedding-ada-002', @@ -413,7 +414,6 @@ describe('InferenceGatewayClient', () => { expect(newClient).toBeInstanceOf(InferenceGatewayClient); expect(newClient).not.toBe(originalClient); - // We can't directly test private properties, but we can test behavior mockFetch.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({}),