import { GatewayError } from '../errors/GatewayError'; import ProviderConfigs from '../providers'; import { endpointStrings, ProviderConfig } from '../providers/types'; import { Options, Params } from '../types/requestBody'; /** * Helper function to set a nested property in an object. * * @param obj - The object on which to set the property. * @param path - The dot-separated path to the property. * @param value - The value to set the property to. */ function setNestedProperty(obj: any, path: string, value: any) { const parts = path.split('.'); let current = obj; for (let i = 0; i < parts.length - 1; i++) { if (!current[parts[i]]) { current[parts[i]] = {}; } current = current[parts[i]]; } current[parts[parts.length - 1]] = value; } const getValue = ( configParam: string, params: Params, paramConfig: any, providerOptions?: Options, ) => { let value = params[configParam as keyof typeof params]; // If a transformation is defined for this parameter, apply it if (paramConfig.transform) { value = paramConfig.transform(params, providerOptions); } if (value === 'lightport-default' && paramConfig && paramConfig.default !== undefined) { // Set the transformed parameter to the default value value = paramConfig.default; } // If a minimum is defined for this parameter and the value is less than this, set the value to the minimum // Also, we should only do this comparison if value is of type 'number' if ( typeof value === 'number' && paramConfig && paramConfig.min !== undefined && value < paramConfig.min ) { value = paramConfig.min; } // If a maximum is defined for this parameter and the value is more than this, set the value to the maximum // Also, we should only do this comparison if value is of type 'number' else if ( typeof value === 'number' && paramConfig && paramConfig.max !== undefined && value > paramConfig.max ) { value = paramConfig.max; } return value; }; export const transformUsingProviderConfig = ( providerConfig: ProviderConfig, params: Params, providerOptions?: Options, ) => { const transformedRequest: { [key: string]: any } = {}; // For each parameter in the provider's configuration for (const configParam in providerConfig) { // Get the config for this parameter let paramConfigs = providerConfig[configParam]; if (!Array.isArray(paramConfigs)) { paramConfigs = [paramConfigs]; } for (const paramConfig of paramConfigs) { // If the parameter is present in the incoming request body if (configParam in params) { // Get the value for this parameter const value = getValue(configParam, params, paramConfig, providerOptions); // Set the transformed parameter to the validated value setNestedProperty(transformedRequest, paramConfig?.param, value); } // If the parameter is not present in the incoming request body but is required, set it to the default value else if (paramConfig && paramConfig.required && paramConfig.default !== undefined) { // if default is a function, call it let value; if (typeof paramConfig.default === 'function') { value = paramConfig.default(params, providerOptions); } else { value = paramConfig.default; } // Set the transformed parameter to the default value setNestedProperty(transformedRequest, paramConfig.param, value); } } } return transformedRequest; }; /** * Transforms the request body to match the structure required by the AI provider. * It also ensures the values for each parameter are within the minimum and maximum * constraints defined in the provider's configuration. If a required parameter is missing, * it assigns the default value from the provider's configuration. * * @param provider - The name of the AI provider. * @param params - The parameters for the request. * @param fn - The function to call on the AI provider. * * @returns The transformed request body. * * @throws {Error} If the provider is not supported. */ const transformToProviderRequestJSON = ( provider: string, params: Params, fn: string, providerOptions: Options, ): { [key: string]: any } => { // Get the configuration for the specified provider let providerConfig = ProviderConfigs[provider]; if (providerConfig.getConfig) { providerConfig = providerConfig.getConfig({ params, fn: fn as endpointStrings, providerOptions, })[fn]; } else { providerConfig = providerConfig[fn]; } if (!providerConfig) { throw new GatewayError(`${fn} is not supported by ${provider}`); } return transformUsingProviderConfig(providerConfig, params, providerOptions); }; const transformToProviderRequestFormData = ( provider: string, params: Params, fn: string, providerOptions?: Options, ): FormData => { let providerConfig = ProviderConfigs[provider]; if (providerConfig.getConfig) { providerConfig = providerConfig.getConfig({ params, fn: fn as endpointStrings, providerOptions, })[fn]; } else { providerConfig = providerConfig[fn]; } const formData = new FormData(); for (const configParam in providerConfig) { let paramConfigs = providerConfig[configParam]; if (!Array.isArray(paramConfigs)) { paramConfigs = [paramConfigs]; } for (const paramConfig of paramConfigs) { if (configParam in params) { const value = getValue(configParam, params, paramConfig, providerOptions); formData.append(paramConfig.param, value); } else if (paramConfig && paramConfig.required && paramConfig.default !== undefined) { let value; if (typeof paramConfig.default === 'function') { value = paramConfig.default(params); } else { value = paramConfig.default; } formData.append(paramConfig.param, value); } } } return formData; }; const transformToProviderRequestBody = ( provider: string, requestBody: ReadableStream, requestHeaders: Record, fn: endpointStrings, ) => { if (ProviderConfigs[provider].getConfig) { return ProviderConfigs[provider] .getConfig?.({ params: {}, fn }) ?.requestTransforms?.[fn]?.(requestBody, requestHeaders); } else { return ProviderConfigs[provider].requestTransforms?.[fn]?.(requestBody, requestHeaders); } }; /** * Transforms the request parameters to the format expected by the provider. * * @param {string} provider - The name of the provider (e.g., 'openai', 'anthropic'). * @param {Params} params - The parameters for the request. * @param {Params | FormData} inputParams - The original input parameters. * @param {endpointStrings} fn - The function endpoint being called (e.g., 'complete', 'chatComplete'). * @returns {Params | FormData} - The transformed request parameters. */ const transformToProviderRequest = ( provider: string, params: Params, requestBody: Params | FormData | ArrayBuffer | ReadableStream, fn: endpointStrings, requestHeaders: Record, providerOptions: Options, ) => { // this returns a ReadableStream if (fn === 'uploadFile') { return transformToProviderRequestBody( provider, requestBody as ReadableStream, requestHeaders, fn, ); } const containsRequestTransform = ProviderConfigs[provider].requestTransforms?.[fn] || ProviderConfigs[provider].getConfig?.({ params, fn, providerOptions })?.requestTransforms?.[fn]; if (containsRequestTransform) { return transformToProviderRequestBody( provider, requestBody as ReadableStream, requestHeaders, fn, ); } if (requestBody instanceof FormData || params instanceof ArrayBuffer) return requestBody; if (fn === 'proxy') { return params; } const providerAPIConfig = ProviderConfigs[provider].api; if ( providerAPIConfig.transformToFormData && providerAPIConfig.transformToFormData({ gatewayRequestBody: params }) ) return transformToProviderRequestFormData(provider, params, fn, providerOptions); return transformToProviderRequestJSON(provider, params, fn, providerOptions); }; export default transformToProviderRequest;