From 4d3a325deee8efb814d9af17a63f4dd3a1729c2f Mon Sep 17 00:00:00 2001 From: Jerry Tian Date: Wed, 9 Apr 2025 23:57:00 -0400 Subject: [PATCH] feat: hacking FAL proxy to support 3rd party API --- .../api_scripts/tuzi_api_test.sh | 33 ++++++++++++++ libs/proxy/src/index.ts | 45 +++++++++++++++---- 2 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 apps/demo-nextjs-app-router/api_scripts/tuzi_api_test.sh diff --git a/apps/demo-nextjs-app-router/api_scripts/tuzi_api_test.sh b/apps/demo-nextjs-app-router/api_scripts/tuzi_api_test.sh new file mode 100644 index 0000000..c9db586 --- /dev/null +++ b/apps/demo-nextjs-app-router/api_scripts/tuzi_api_test.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +# curl -v --location --request POST 'https://api.tu-zi.com/v1/chat/completions' \ +# --header 'Content-Type: application/json' \ +# --header 'Authorization: Bearer sk-...' \ +# --data-raw '{ +# "model": "gpt-4-gizmo-g-2fkFE8rbu", +# "messages": [ +# { +# "role": "user", +# "content": "你是谁" +# } +# ], +# "stream": false +# }' + +# echo + +curl -v --location --request POST 'https://animator-gg-api.ca2324.servep2p.com:8443/api/fal/proxy' \ +--header 'Content-Type: application/json' \ +--header 'x-fal-target-url: https://api.tu-zi.com/v1/chat/completions' \ +--data-raw '{ + "model": "gpt-4-gizmo-g-2fkFE8rbu", + "messages": [ + { + "role": "user", + "content": "你是谁" + } + ], + "stream": false +}' + +echo \ No newline at end of file diff --git a/libs/proxy/src/index.ts b/libs/proxy/src/index.ts index 0b01966..7b62e22 100644 --- a/libs/proxy/src/index.ts +++ b/libs/proxy/src/index.ts @@ -10,6 +10,9 @@ export type HeaderValue = string | string[] | undefined | null; const FAL_URL_REG_EXP = /(\.|^)fal\.(run|ai)$/; +const TUZI_API_KEY = process.env.TUZI_API_KEY; +const TUZI_URL_REG_EXP = /(\.|^)tu-zi\.com$/; + /** * The proxy behavior that is passed to the proxy handler. This is a subset of * request objects that are used by different frameworks, like Express and NextJS. @@ -43,7 +46,6 @@ function singleHeaderValue(value: HeaderValue): string | undefined { } return value; } - function getFalKey(): string | undefined { if (FAL_KEY) { return FAL_KEY; @@ -54,6 +56,18 @@ function getFalKey(): string | undefined { return undefined; } +function getFalKeyForTargetHost(targetHost: string): string | undefined { + if (FAL_URL_REG_EXP.test(targetHost)) { + return getFalKey(); + } + + if (TUZI_URL_REG_EXP.test(targetHost)) { + return TUZI_API_KEY; + } + + return undefined; +} + const EXCLUDED_HEADERS = ["content-length", "content-encoding"]; /** @@ -74,14 +88,29 @@ export async function handleRequest( } const urlHost = new URL(targetUrl).host; - if (!FAL_URL_REG_EXP.test(urlHost)) { - return behavior.respondWith(412, `Invalid ${TARGET_URL_HEADER} header`); + var targetApiKey = undefined; + var targetApiAuthHeader = undefined; + if (FAL_URL_REG_EXP.test(urlHost)) { + targetApiKey = getFalKey(); + targetApiAuthHeader = `Key ${targetApiKey}`; + } else if (TUZI_URL_REG_EXP.test(urlHost)) { + targetApiKey = TUZI_API_KEY; + targetApiAuthHeader = `Bearer ${targetApiKey}`; + } else { + return behavior.respondWith( + 412, + `Invalid ${TARGET_URL_HEADER} header with host: ${urlHost}`, + ); } - const falKey = behavior.resolveApiKey - ? await behavior.resolveApiKey() - : getFalKey(); - if (!falKey) { + // const falKey = behavior.resolveApiKey + // ? await behavior.resolveApiKey() + // : getFalKeyForTargetHost(urlHost); + // if (!falKey) { + // return behavior.respondWith(401, "Missing fal.ai credentials"); + // } + + if (!targetApiKey) { return behavior.respondWith(401, "Missing fal.ai credentials"); } @@ -101,7 +130,7 @@ export async function handleRequest( ...headers, authorization: singleHeaderValue(behavior.getHeader("authorization")) ?? - `Key ${falKey}`, + targetApiAuthHeader, accept: "application/json", "content-type": "application/json", "user-agent": userAgent,