UNPKG

siliconflow-serverless-client

Version:

![@siliconflow/siliconflow-js npm package](https://img.shields.io/npm/v/@siliconflow/siliconflow-js?color=%25237527D7&label=@siliconflow/siliconflow-js&style=flat-square)

168 lines (146 loc) 5.02 kB
export const TARGET_URL_HEADER = "x-siliconflow-target-url"; export const DEFAULT_PROXY_ROUTE = "/api/example/sdk"; const SILICONFLOW_API_KEY = process.env.SILICONFLOW_API_KEY; export type HeaderValue = string | string[] | undefined | null; const SILICONFLOW_URL_REG_EXP = /(\.|^)siliconflow\.(com)$|192\.168\.1\.102||10\.8\.0\.\d/; /** * 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. */ export interface ProxyBehavior<ResponseType> { id: string; method: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any respondWith(status: number, data: string | any): ResponseType; getHeaders(): Record<string, HeaderValue>; getHeader(name: string): HeaderValue; sendHeader(name: string, value: string): void; getBody(): Promise<string | undefined>; resolveApiKey?: () => Promise<string | undefined>; } /** * Utility to get a header value as `string` from a Headers object. * * @private * @param request the header value. * @returns the header value as `string` or `undefined` if the header is not set. */ function singleHeaderValue(value: HeaderValue): string | undefined { if (!value) { return undefined; } if (Array.isArray(value)) { return value[0]; } return value; } function HeaderValueHandle(value: HeaderValue): string | undefined { let token: string | undefined; if (!value) { return undefined; } if (typeof value === "string" && value.includes("authjs.session-token")) { const cookiesArray = value.split(";"); for (const cookie of cookiesArray) { if (cookie.includes("authjs.session-token")) { token = cookie.split("=")?.[1]; break; } } } return token; } function getSILICONFLOWKey(): string | undefined { if (SILICONFLOW_API_KEY) { return SILICONFLOW_API_KEY; } return undefined; } const EXCLUDED_HEADERS = ["content-length", "content-encoding"]; /** * A request handler that proxies the request to the fal-serverless * endpoint. This is useful so client-side calls to the fal-serverless endpoint * can be made without CORS issues and the correct credentials can be added * effortlessly. * * @param behavior the request proxy behavior. * @returns Promise<any> the promise that will be resolved once the request is done. */ export async function handleRequest<ResponseType>( behavior: ProxyBehavior<ResponseType>, ) { // console.log("[behavior]", behavior.getHeaders()); const targetUrl = singleHeaderValue(behavior.getHeader(TARGET_URL_HEADER)); if (!targetUrl) { return behavior.respondWith( 400, `Missing the ${TARGET_URL_HEADER} header`, ); } const urlHost = new URL(targetUrl).host; if (!SILICONFLOW_URL_REG_EXP.test(urlHost)) { return behavior.respondWith(412, `Invalid ${TARGET_URL_HEADER} header`); } const falKey = behavior.resolveApiKey ? await behavior.resolveApiKey() : getSILICONFLOWKey(); if (!falKey) { return behavior.respondWith( 401, "Missing SILICONFLOWcost credentials", ); } // pass over headers prefixed with x-fal-* const headers: Record<string, HeaderValue> = {}; Object.keys(behavior.getHeaders()).forEach((key) => { // if (key.toLowerCase().startsWith("x-SILICONFLOW-")) { headers[key.toLowerCase()] = behavior.getHeader(key); // } }); // console.log("[headers1]", headers); const proxyUserAgent = `@SILICONFLOW-ai/serverless-proxy/${behavior.id}`; // console.log("[targetUrl]", HeaderValueHandle(behavior.getHeader("cookie"))); const userAgent = singleHeaderValue(behavior.getHeader("user-agent")); const res = await fetch(targetUrl, { method: behavior.method, headers: { ...headers, authorization: singleHeaderValue(behavior.getHeader("authorization")) ?? `Bearer ${falKey}`, accept: "application/json", "content-type": "application/json", "user-agent": userAgent, "x-SILICONFLOW-client-proxy": proxyUserAgent, } as HeadersInit, body: behavior.method?.toUpperCase() === "GET" ? undefined : await behavior.getBody(), }); // console.log("[res]", res); // copy headers from fal to the proxied response res.headers.forEach((value, key) => { if (!EXCLUDED_HEADERS.includes(key.toLowerCase())) { behavior.sendHeader(key, value); } }); if (res.headers.get("content-type")?.includes("application/json")) { const data = await res.json(); return behavior.respondWith(res.status, data); } const data = await res.text(); return behavior.respondWith(res.status, data); } export function fromHeaders( headers: Headers, ): Record<string, string | string[]> { // TODO once Header.entries() is available, use that instead // Object.fromEntries(headers.entries()); const result: Record<string, string | string[]> = {}; headers.forEach((value, key) => { result[key] = value; }); return result; }