File size: 8,330 Bytes
c7052c4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
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<string, string>,
  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<string, string>,
  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;