diff --git a/src/handlers/handlerUtils.ts b/src/handlers/handlerUtils.ts index d74a9c41d..d048a4964 100644 --- a/src/handlers/handlerUtils.ts +++ b/src/handlers/handlerUtils.ts @@ -857,7 +857,6 @@ export function constructConfigFromRequestHeaders( azureApiVersion: requestHeaders[`x-${POWERED_BY}-azure-api-version`], azureEndpointName: requestHeaders[`x-${POWERED_BY}-azure-endpoint-name`], azureFoundryUrl: requestHeaders[`x-${POWERED_BY}-azure-foundry-url`], - azureExtraParams: requestHeaders[`x-${POWERED_BY}-azure-extra-params`], azureAdToken: requestHeaders[`x-${POWERED_BY}-azure-ad-token`], azureAuthMode: requestHeaders[`x-${POWERED_BY}-azure-auth-mode`], azureManagedClientId: @@ -867,6 +866,7 @@ export function constructConfigFromRequestHeaders( requestHeaders[`x-${POWERED_BY}-azure-entra-client-secret`], azureEntraTenantId: requestHeaders[`x-${POWERED_BY}-azure-entra-tenant-id`], azureEntraScope: requestHeaders[`x-${POWERED_BY}-azure-entra-scope`], + azureExtraParameters: requestHeaders[`x-${POWERED_BY}-azure-extra-params`], }; const awsConfig = { diff --git a/src/providers/azure-ai-inference/api.ts b/src/providers/azure-ai-inference/api.ts index 782f8d451..01e434343 100644 --- a/src/providers/azure-ai-inference/api.ts +++ b/src/providers/azure-ai-inference/api.ts @@ -39,14 +39,14 @@ const AzureAIInferenceAPI: ProviderAPIConfig = { headers: async ({ providerOptions, fn }) => { const { apiKey, - azureExtraParams, + azureExtraParameters, azureDeploymentName, azureAdToken, azureAuthMode, } = providerOptions; const headers: Record = { - 'extra-parameters': azureExtraParams ?? 'drop', + 'extra-parameters': azureExtraParameters ?? 'drop', ...(azureDeploymentName && { 'azureml-model-deployment': azureDeploymentName, }), diff --git a/src/providers/bedrock/api.ts b/src/providers/bedrock/api.ts index 8a362c851..dbba0d6c5 100644 --- a/src/providers/bedrock/api.ts +++ b/src/providers/bedrock/api.ts @@ -1,10 +1,10 @@ import { Context } from 'hono'; -import { Options } from '../../types/requestBody'; +import { Options, Params } from '../../types/requestBody'; import { endpointStrings, ProviderAPIConfig } from '../types'; import { bedrockInvokeModels } from './constants'; import { + getAwsEndpointDomain, generateAWSHeaders, - getAssumedRoleCredentials, getFoundationModelFromInferenceProfile, providerAssumedRoleCredentials, } from './utils'; @@ -18,6 +18,7 @@ interface BedrockAPIConfigInterface extends Omit { transformedRequestBody: Record | string; transformedRequestUrl: string; gatewayRequestBody?: Params; + headers?: Record; }) => Promise> | Record; } @@ -66,7 +67,14 @@ const ENDPOINTS_TO_ROUTE_TO_S3 = [ 'initiateMultipartUpload', ]; -const getMethod = (fn: endpointStrings, transformedRequestUrl: string) => { +const getMethod = ( + fn: endpointStrings, + transformedRequestUrl: string, + c: Context +) => { + if (fn === 'proxy') { + return c.req.method; + } if (fn === 'uploadFile') { const url = new URL(transformedRequestUrl); return url.searchParams.get('partNumber') ? 'PUT' : 'POST'; @@ -121,20 +129,20 @@ const BedrockAPIConfig: BedrockAPIConfigInterface = { gatewayRequestURL.split('/v1/files/')[1] ); const bucketName = s3URL.replace('s3://', '').split('/')[0]; - return `https://${bucketName}.s3.${providerOptions.awsRegion || 'us-east-1'}.amazonaws.com`; + return `https://${bucketName}.s3.${providerOptions.awsRegion || 'us-east-1'}.${getAwsEndpointDomain(c)}`; } if (fn === 'retrieveFileContent') { const s3URL = decodeURIComponent( gatewayRequestURL.split('/v1/files/')[1] ); const bucketName = s3URL.replace('s3://', '').split('/')[0]; - return `https://${bucketName}.s3.${providerOptions.awsRegion || 'us-east-1'}.amazonaws.com`; + return `https://${bucketName}.s3.${providerOptions.awsRegion || 'us-east-1'}.${getAwsEndpointDomain(c)}`; } if (fn === 'uploadFile') - return `https://${providerOptions.awsS3Bucket}.s3.${providerOptions.awsRegion || 'us-east-1'}.amazonaws.com`; + return `https://${providerOptions.awsS3Bucket}.s3.${providerOptions.awsRegion || 'us-east-1'}.${getAwsEndpointDomain(c)}`; const isAWSControlPlaneEndpoint = fn && AWS_CONTROL_PLANE_ENDPOINTS.includes(fn); - return `https://${isAWSControlPlaneEndpoint ? 'bedrock' : 'bedrock-runtime'}.${providerOptions.awsRegion || 'us-east-1'}.amazonaws.com`; + return `https://${isAWSControlPlaneEndpoint ? 'bedrock' : 'bedrock-runtime'}.${providerOptions.awsRegion || 'us-east-1'}.${getAwsEndpointDomain(c)}`; }, headers: async ({ c, @@ -142,15 +150,26 @@ const BedrockAPIConfig: BedrockAPIConfigInterface = { providerOptions, transformedRequestBody, transformedRequestUrl, + gatewayRequestBody, // for proxy use the passed body blindly + headers: requestHeaders, }) => { - const method = getMethod(fn as endpointStrings, transformedRequestUrl); - const service = getService(fn as endpointStrings); + const { awsService } = providerOptions; + const method = + c.get('method') || // method set specifically into context + getMethod(fn as endpointStrings, transformedRequestUrl, c); // method calculated + const service = awsService || getService(fn as endpointStrings); - const headers: Record = { - 'content-type': 'application/json', - }; + let headers: Record = {}; - if (method === 'PUT' || method === 'GET') { + if (fn === 'proxy' && service !== 'bedrock') { + headers = { ...(requestHeaders ?? {}) }; + } else { + headers = { + 'content-type': 'application/json', + }; + } + + if ((method === 'PUT' || method === 'GET') && fn !== 'proxy') { delete headers['content-type']; } @@ -160,7 +179,8 @@ const BedrockAPIConfig: BedrockAPIConfigInterface = { await providerAssumedRoleCredentials(c, providerOptions); } - let finalRequestBody = transformedRequestBody; + let finalRequestBody = + fn === 'proxy' ? gatewayRequestBody : transformedRequestBody; if (['cancelFinetune', 'cancelBatch'].includes(fn as endpointStrings)) { // Cancel doesn't require any body, but fetch is sending empty body, to match the signature this block is required. @@ -183,7 +203,6 @@ const BedrockAPIConfig: BedrockAPIConfigInterface = { fn, gatewayRequestBodyJSON: gatewayRequestBody, gatewayRequestURL, - c, }) => { if (fn === 'retrieveFile') { const fileId = decodeURIComponent( diff --git a/src/providers/bedrock/chatComplete.ts b/src/providers/bedrock/chatComplete.ts index 41a22066e..cecd65767 100644 --- a/src/providers/bedrock/chatComplete.ts +++ b/src/providers/bedrock/chatComplete.ts @@ -62,7 +62,7 @@ export interface BedrockChatCompletionsParams extends Params { } export interface BedrockConverseAnthropicChatCompletionsParams - extends Omit { + extends BedrockChatCompletionsParams { anthropic_version?: string; user?: string; thinking?: { @@ -511,9 +511,6 @@ export const BedrockChatCompleteResponseTransform: ( } if ('output' in response) { - const cacheReadInputTokens = response.usage?.cacheReadInputTokens || 0; - const cacheWriteInputTokens = response.usage?.cacheWriteInputTokens || 0; - let content: string = ''; content = response.output.message.content .filter((item) => item.text) @@ -523,6 +520,9 @@ export const BedrockChatCompleteResponseTransform: ( ? transformContentBlocks(response.output.message.content) : undefined; + const cacheReadInputTokens = response.usage?.cacheReadInputTokens || 0; + const cacheWriteInputTokens = response.usage?.cacheWriteInputTokens || 0; + const responseObj: ChatCompletionResponse = { id: Date.now().toString(), object: 'chat.completion', @@ -605,7 +605,6 @@ export const BedrockChatCompleteStreamChunkTransform: ( streamState.currentToolCallIndex = -1; } - // final chunk if (parsedChunk.usage) { const cacheReadInputTokens = parsedChunk.usage?.cacheReadInputTokens || 0; const cacheWriteInputTokens = parsedChunk.usage?.cacheWriteInputTokens || 0; @@ -639,9 +638,8 @@ export const BedrockChatCompleteStreamChunkTransform: ( }, // we only want to be sending this for anthropic models and this is not openai compliant ...((cacheReadInputTokens > 0 || cacheWriteInputTokens > 0) && { - cache_read_input_tokens: parsedChunk.usage.cacheReadInputTokens, - cache_creation_input_tokens: - parsedChunk.usage.cacheWriteInputTokens, + cache_read_input_tokens: cacheReadInputTokens, + cache_creation_input_tokens: cacheWriteInputTokens, }), }, })}\n\n`, diff --git a/src/providers/bedrock/constants.ts b/src/providers/bedrock/constants.ts index d90bd82e7..aec7da6db 100644 --- a/src/providers/bedrock/constants.ts +++ b/src/providers/bedrock/constants.ts @@ -1,3 +1,15 @@ +export const BEDROCK_STABILITY_V1_MODELS = [ + 'stable-diffusion-xl-v0', + 'stable-diffusion-xl-v1', +]; + +export const bedrockInvokeModels = [ + 'cohere.command-light-text-v14', + 'cohere.command-text-v14', + 'ai21.j2-mid-v1', + 'ai21.j2-ultra-v1', +]; + export const LLAMA_2_SPECIAL_TOKENS = { BEGINNING_OF_SENTENCE: '', END_OF_SENTENCE: '', @@ -34,15 +46,3 @@ export const MISTRAL_CONTROL_TOKENS = { MIDDLE: '[MIDDLE]', SUFFIX: '[SUFFIX]', }; - -export const BEDROCK_STABILITY_V1_MODELS = [ - 'stable-diffusion-xl-v0', - 'stable-diffusion-xl-v1', -]; - -export const bedrockInvokeModels = [ - 'cohere.command-light-text-v14', - 'cohere.command-text-v14', - 'ai21.j2-mid-v1', - 'ai21.j2-ultra-v1', -]; diff --git a/src/providers/bedrock/createFinetune.ts b/src/providers/bedrock/createFinetune.ts index d44a1a501..ca121af9e 100644 --- a/src/providers/bedrock/createFinetune.ts +++ b/src/providers/bedrock/createFinetune.ts @@ -46,7 +46,7 @@ export const BedrockCreateFinetuneConfig: ProviderConfig = { return undefined; } return { - s3Uri: decodeURIComponent(value.validation_file), + s3Uri: decodeURIComponent(value.validation_file ?? ''), }; }, }, diff --git a/src/providers/bedrock/embed.ts b/src/providers/bedrock/embed.ts index 5d804b45e..8f7b7e053 100644 --- a/src/providers/bedrock/embed.ts +++ b/src/providers/bedrock/embed.ts @@ -60,6 +60,12 @@ export const BedrockCohereEmbedConfig: ProviderConfig = { }, }; +const g1EmbedModels = [ + 'amazon.titan-embed-g1-text-02', + 'amazon.titan-embed-text-v1', + 'amazon.titan-embed-image-v1', +]; + export const BedrockTitanEmbedConfig: ProviderConfig = { input: [ { @@ -117,6 +123,8 @@ export const BedrockTitanEmbedConfig: ProviderConfig = { param: 'embeddingTypes', required: false, transform: (params: any): string[] | undefined => { + const model = params.foundationModel || params.model || ''; + if (g1EmbedModels.includes(model)) return undefined; if (Array.isArray(params.encoding_format)) return params.encoding_format; else if (typeof params.encoding_format === 'string') return [params.encoding_format]; diff --git a/src/providers/bedrock/getBatchOutput.ts b/src/providers/bedrock/getBatchOutput.ts index 70ff945d0..c6ec8fa1d 100644 --- a/src/providers/bedrock/getBatchOutput.ts +++ b/src/providers/bedrock/getBatchOutput.ts @@ -5,6 +5,7 @@ import { BedrockGetBatchResponse } from './types'; import { getOctetStreamToOctetStreamTransformer } from '../../handlers/streamHandlerUtils'; import { BedrockUploadFileResponseTransforms } from './uploadFileUtils'; import { BEDROCK } from '../../globals'; +import { getAwsEndpointDomain } from './utils'; const getModelProvider = (modelId: string) => { let provider = ''; @@ -89,7 +90,7 @@ export const BedrockGetBatchOutputRequestHandler = async ({ const awsS3ObjectKey = `${primaryKey}${jobId}/${inputS3URIParts[inputS3URIParts.length - 1]}.out`; const awsModelProvider = batchDetails.modelId; - const s3FileURL = `https://${awsS3Bucket}.s3.${awsRegion}.amazonaws.com/${awsS3ObjectKey}`; + const s3FileURL = `https://${awsS3Bucket}.s3.${awsRegion}.${getAwsEndpointDomain(c)}/${awsS3ObjectKey}`; const s3FileHeaders = await BedrockAPIConfig.headers({ c, providerOptions, diff --git a/src/providers/bedrock/listBatches.ts b/src/providers/bedrock/listBatches.ts index e4af72f15..fc83ae278 100644 --- a/src/providers/bedrock/listBatches.ts +++ b/src/providers/bedrock/listBatches.ts @@ -28,12 +28,8 @@ export const BedrockListBatchesResponseTransform = ( output_file_id: encodeURIComponent( batch.outputDataConfig.s3OutputDataConfig.s3Uri ), - finalizing_at: batch.endTime - ? new Date(batch.endTime).getTime() - : undefined, - expires_at: batch.jobExpirationTime - ? new Date(batch.jobExpirationTime).getTime() - : undefined, + finalizing_at: new Date(batch.endTime).getTime(), + expires_at: new Date(batch.jobExpirationTime).getTime(), })); return { diff --git a/src/providers/bedrock/listFinetunes.ts b/src/providers/bedrock/listFinetunes.ts index 872a221e1..594b0bec8 100644 --- a/src/providers/bedrock/listFinetunes.ts +++ b/src/providers/bedrock/listFinetunes.ts @@ -10,6 +10,7 @@ export const BedrockListFinetuneResponseTransform: ( if (responseStatus !== 200) { return BedrockErrorResponseTransform(response) || response; } + const records = response?.modelCustomizationJobSummaries as BedrockFinetuneRecord[]; const openaiRecords = records.map(bedrockFinetuneToOpenAI); diff --git a/src/providers/bedrock/types.ts b/src/providers/bedrock/types.ts index c7ea9d039..051f1335d 100644 --- a/src/providers/bedrock/types.ts +++ b/src/providers/bedrock/types.ts @@ -81,6 +81,16 @@ export interface BedrockInferenceProfile { type: string; } +// https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html#API_runtime_Converse_ResponseSyntax +export enum BEDROCK_STOP_REASON { + end_turn = 'end_turn', + tool_use = 'tool_use', + max_tokens = 'max_tokens', + stop_sequence = 'stop_sequence', + guardrail_intervened = 'guardrail_intervened', + content_filtered = 'content_filtered', +} + export interface BedrockMessagesParams extends MessageCreateParamsBase { additionalModelRequestFields?: Record; additional_model_request_fields?: Record; diff --git a/src/providers/bedrock/uploadFileUtils.ts b/src/providers/bedrock/uploadFileUtils.ts index dfbc95717..b01219fee 100644 --- a/src/providers/bedrock/uploadFileUtils.ts +++ b/src/providers/bedrock/uploadFileUtils.ts @@ -830,6 +830,10 @@ interface BedrockAnthropicChatCompleteResponse { stop_reason: string; model: string; stop_sequence: null | string; + usage: { + input_tokens: number; + output_tokens: number; + }; } export const BedrockAnthropicChatCompleteResponseTransform: ( @@ -874,9 +878,10 @@ export const BedrockAnthropicChatCompleteResponseTransform: ( }, ], usage: { - prompt_tokens: 0, - completion_tokens: 0, - total_tokens: 0, + prompt_tokens: response.usage.input_tokens, + completion_tokens: response.usage.output_tokens, + total_tokens: + response.usage.input_tokens + response.usage.output_tokens, }, }; } @@ -933,6 +938,7 @@ export const BedrockMistralChatCompleteResponseTransform: ( finish_reason: response.outputs[0].stop_reason, }, ], + // mistral not sending usage. usage: { prompt_tokens: 0, completion_tokens: 0, diff --git a/src/providers/bedrock/utils.ts b/src/providers/bedrock/utils.ts index 9ee92e8d5..963d18e75 100644 --- a/src/providers/bedrock/utils.ts +++ b/src/providers/bedrock/utils.ts @@ -13,6 +13,10 @@ import { GatewayError } from '../../errors/GatewayError'; import { BedrockFinetuneRecord, BedrockInferenceProfile } from './types'; import { FinetuneRequest } from '../types'; import { BEDROCK } from '../../globals'; +import { Environment } from '../../utils/env'; + +export const getAwsEndpointDomain = (c: Context) => + Environment(c).AWS_ENDPOINT_DOMAIN || 'amazonaws.com'; export const generateAWSHeaders = async ( body: Record | string | undefined, diff --git a/src/types/requestBody.ts b/src/types/requestBody.ts index 1ca59076f..e23b40b44 100644 --- a/src/types/requestBody.ts +++ b/src/types/requestBody.ts @@ -95,6 +95,7 @@ export interface Options { awsBedrockModel?: string; awsServerSideEncryption?: string; awsServerSideEncryptionKMSKeyId?: string; + awsService?: string; foundationModel?: string; /** Sagemaker specific */ @@ -131,22 +132,22 @@ export interface Options { beforeRequestHooks?: HookObject[]; defaultInputGuardrails?: HookObject[]; defaultOutputGuardrails?: HookObject[]; - /** OpenAI specific */ openaiProject?: string; openaiOrganization?: string; openaiBeta?: string; - /** Azure Inference Specific */ - azureDeploymentName?: string; azureApiVersion?: string; - azureExtraParams?: string; azureFoundryUrl?: string; + azureExtraParameters?: string; + azureDeploymentName?: string; /** The parameter to determine if extra non-openai compliant fields should be returned in response */ strictOpenAiCompliance?: boolean; + /** Parameter to determine if fim/completions endpoint is to be used */ mistralFimCompletion?: string; + /** Anthropic specific headers */ anthropicBeta?: string; anthropicVersion?: string; @@ -425,7 +426,6 @@ export interface Params { // Google Vertex AI specific safety_settings?: any; // Anthropic specific - anthropic_beta?: string; anthropic_version?: string; thinking?: { type?: string; diff --git a/src/utils/env.ts b/src/utils/env.ts new file mode 100644 index 000000000..2969e5133 --- /dev/null +++ b/src/utils/env.ts @@ -0,0 +1,131 @@ +import fs from 'fs'; +import { Context } from 'hono'; +import { env, getRuntimeKey } from 'hono/adapter'; + +const isNodeInstance = getRuntimeKey() == 'node'; +let path: any; +if (isNodeInstance) { + path = await import('path'); +} + +export function getValueOrFileContents(value?: string, ignore?: boolean) { + if (!value || ignore) return value; + + try { + // Check if value looks like a file path + if ( + value.startsWith('/') || + value.startsWith('./') || + value.startsWith('../') + ) { + // Resolve the path (handle relative paths) + const resolvedPath = path.resolve(value); + + // Check if file exists + if (fs.existsSync(resolvedPath)) { + // File exists, read and return its contents + return fs.readFileSync(resolvedPath, 'utf8').trim(); + } + } + + // If not a file path or file doesn't exist, return value as is + return value; + } catch (error: any) { + console.log(`Error reading file at ${value}: ${error.message}`); + // Return the original value if there's an error + return value; + } +} + +const nodeEnv = { + NODE_ENV: getValueOrFileContents(process.env.NODE_ENV, true), + PORT: getValueOrFileContents(process.env.PORT) || 8787, + + TLS_KEY_PATH: getValueOrFileContents(process.env.TLS_KEY_PATH, true), + TLS_CERT_PATH: getValueOrFileContents(process.env.TLS_CERT_PATH, true), + TLS_CA_PATH: getValueOrFileContents(process.env.TLS_CA_PATH, true), + + AWS_ACCESS_KEY_ID: getValueOrFileContents(process.env.AWS_ACCESS_KEY_ID), + AWS_SECRET_ACCESS_KEY: getValueOrFileContents( + process.env.AWS_SECRET_ACCESS_KEY + ), + AWS_SESSION_TOKEN: getValueOrFileContents(process.env.AWS_SESSION_TOKEN), + AWS_ROLE_ARN: getValueOrFileContents(process.env.AWS_ROLE_ARN), + AWS_PROFILE: getValueOrFileContents(process.env.AWS_PROFILE, true), + AWS_WEB_IDENTITY_TOKEN_FILE: getValueOrFileContents( + process.env.AWS_WEB_IDENTITY_TOKEN_FILE, + true + ), + AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: getValueOrFileContents( + process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI, + true + ), + AWS_ASSUME_ROLE_ACCESS_KEY_ID: getValueOrFileContents( + process.env.AWS_ASSUME_ROLE_ACCESS_KEY_ID + ), + AWS_ASSUME_ROLE_SECRET_ACCESS_KEY: getValueOrFileContents( + process.env.AWS_ASSUME_ROLE_SECRET_ACCESS_KEY + ), + AWS_ASSUME_ROLE_REGION: getValueOrFileContents( + process.env.AWS_ASSUME_ROLE_REGION + ), + AWS_REGION: getValueOrFileContents(process.env.AWS_REGION), + AWS_ENDPOINT_DOMAIN: getValueOrFileContents(process.env.AWS_ENDPOINT_DOMAIN), + AWS_IMDS_V1: getValueOrFileContents(process.env.AWS_IMDS_V1), + + AZURE_AUTH_MODE: getValueOrFileContents(process.env.AZURE_AUTH_MODE), + AZURE_ENTRA_CLIENT_ID: getValueOrFileContents( + process.env.AZURE_ENTRA_CLIENT_ID + ), + AZURE_ENTRA_CLIENT_SECRET: getValueOrFileContents( + process.env.AZURE_ENTRA_CLIENT_SECRET + ), + AZURE_ENTRA_TENANT_ID: getValueOrFileContents( + process.env.AZURE_ENTRA_TENANT_ID + ), + AZURE_MANAGED_CLIENT_ID: getValueOrFileContents( + process.env.AZURE_MANAGED_CLIENT_ID + ), + AZURE_MANAGED_VERSION: getValueOrFileContents( + process.env.AZURE_MANAGED_VERSION + ), + AZURE_IDENTITY_ENDPOINT: getValueOrFileContents( + process.env.IDENTITY_ENDPOINT, + true + ), + AZURE_MANAGED_IDENTITY_HEADER: getValueOrFileContents( + process.env.IDENTITY_HEADER + ), + + SSE_ENCRYPTION_TYPE: getValueOrFileContents(process.env.SSE_ENCRYPTION_TYPE), + KMS_KEY_ID: getValueOrFileContents(process.env.KMS_KEY_ID), + KMS_BUCKET_KEY_ENABLED: getValueOrFileContents( + process.env.KMS_BUCKET_KEY_ENABLED + ), + KMS_ENCRYPTION_CONTEXT: getValueOrFileContents( + process.env.KMS_ENCRYPTION_CONTEXT + ), + KMS_ENCRYPTION_ALGORITHM: getValueOrFileContents( + process.env.KMS_ENCRYPTION_ALGORITHM + ), + KMS_ENCRYPTION_CUSTOMER_KEY: getValueOrFileContents( + process.env.KMS_ENCRYPTION_CUSTOMER_KEY + ), + KMS_ENCRYPTION_CUSTOMER_KEY_MD5: getValueOrFileContents( + process.env.KMS_ENCRYPTION_CUSTOMER_KEY_MD5 + ), + KMS_ROLE_ARN: getValueOrFileContents(process.env.KMS_ROLE_ARN), + + HTTP_PROXY: getValueOrFileContents(process.env.HTTP_PROXY), + HTTPS_PROXY: getValueOrFileContents(process.env.HTTPS_PROXY), +}; + +export const Environment = (c?: Context) => { + if (isNodeInstance) { + return nodeEnv; + } + if (c) { + return env(c); + } + return {}; +};