From b8d0c2d034e0374fb49514c153cec40e8a8fdbd1 Mon Sep 17 00:00:00 2001 From: Simon Ma Date: Mon, 18 Dec 2023 05:59:35 +0000 Subject: [PATCH] init --- .gitignore | 3 + README.md | 9 +- next-env.d.ts | 5 + next.config.mjs | 19 +++ package.json | 19 +++ pages/api/proxy.ts | 20 +++ pnpm-lock.yaml | 311 ++++++++++++++++++++++++++++++++++++++++++ src/handle-request.ts | 56 ++++++++ tsconfig.json | 30 ++++ 9 files changed, 471 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 next-env.d.ts create mode 100644 next.config.mjs create mode 100644 package.json create mode 100644 pages/api/proxy.ts create mode 100644 pnpm-lock.yaml create mode 100644 src/handle-request.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53d2aa8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ + +node_modules/ +.next/ diff --git a/README.md b/README.md index f28ddcb..cf5f09c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ + # palm-proxy -Palm API proxy on Vercel Edge +Google PaLM API proxy on Vercel Edge + +## Deploy + +### Deploy With Vercel + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fantergone%2Fpalm-proxy) diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..4f11a03 --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/next.config.mjs b/next.config.mjs new file mode 100644 index 0000000..45c2ba6 --- /dev/null +++ b/next.config.mjs @@ -0,0 +1,19 @@ +/** @type {import('next').NextConfig} */ +const config = { + async rewrites() { + return [ + { + source: "/:path*", + "has": [ + { + "type": "query", + "key": "key", + } + ], + destination: "/api/proxy" + }, + ]; + }, +}; + +export default config; diff --git a/package.json b/package.json new file mode 100644 index 0000000..a707dec --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "palm-proxy", + "version": "0.0.0", + "devDependencies": { + "@types/node": "latest", + "@types/react": "latest", + "typescript": "^5.3.3" + }, + "private": true, + "scripts": { + "next": "next", + "build": "next build" + }, + "dependencies": { + "next": "canary", + "react": "latest", + "react-dom": "latest" + } +} \ No newline at end of file diff --git a/pages/api/proxy.ts b/pages/api/proxy.ts new file mode 100644 index 0000000..d5986cb --- /dev/null +++ b/pages/api/proxy.ts @@ -0,0 +1,20 @@ +import handleRequest from "../../src/handle-request"; + +export const config = { + runtime: "edge", + // Available languages and regions for Google AI Studio and Gemini API + // https://ai.google.dev/available_regions + // https://vercel.com/docs/concepts/edge-network/regions + regions: [ + "cle1", + "iad1", + "pdx1", + "sfo1", + "sin1", + "syd1", + "hnd1", + "kix1", + ], +}; + +export default handleRequest; \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..5e25be9 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,311 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + next: + specifier: canary + version: 14.0.5-canary.17(react-dom@18.2.0)(react@18.2.0) + react: + specifier: latest + version: 18.2.0 + react-dom: + specifier: latest + version: 18.2.0(react@18.2.0) + +devDependencies: + '@types/node': + specifier: latest + version: 20.10.5 + '@types/react': + specifier: latest + version: 18.2.45 + typescript: + specifier: ^5.3.3 + version: 5.3.3 + +packages: + + /@next/env@14.0.5-canary.17: + resolution: {integrity: sha512-YdmiBGjz18ArskwnPB7U7AwTUO7KjJ1Vn64AdAuE4pQqfRzwIyfz2VL83dsB12njoskhN95XOUBstOYUv2RF4A==} + dev: false + + /@next/swc-darwin-arm64@14.0.5-canary.17: + resolution: {integrity: sha512-/sqYvmmSV4388UVGmJrOwcTeKosUPYWBcTQ+YYS7i9gVY2aPaMlZcCXEXsuwOg5Tyav/jd0URWXuh7N9fpVqbw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@next/swc-darwin-x64@14.0.5-canary.17: + resolution: {integrity: sha512-JjXrN7q6R1ys3hWUEtyfvjiB2hCxFeAtSq4GVkpugPQH9J4jLXgaxEcTV54ebU5Oh5Fhnazrw6iEJ3Kte9PZZQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-arm64-gnu@14.0.5-canary.17: + resolution: {integrity: sha512-HTF4ib0YytW8VTcGe6+dqnJt4fE505HjN9z9U1e/OSiYcMs0xd9z5a7f/VVdez3tCt7JYbmdt5aSRebyghk/1A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-arm64-musl@14.0.5-canary.17: + resolution: {integrity: sha512-KPiZw8nvlU94MJtlXMyN5tNPJkJbRy0obHwm69LevAg/AkS7V5RJ/p/4UESPzFJ6qRfLa2kuT6X9xQPwHleGzQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-x64-gnu@14.0.5-canary.17: + resolution: {integrity: sha512-B0sw8r+Tg/C9uPYFNiEdGnNIOYZgnb2hnLZrsYrcho8IN+ZpMqckaJg9CmJd/y7ABEGy2IeuVIyG3xUv/RxrMg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-x64-musl@14.0.5-canary.17: + resolution: {integrity: sha512-Nj+xxWoe9weA8Q9+tJFFARDT7fHg+Ep2PPEo+Wyh/o74K2VKfoChAm/1VwT5QcDKOmK+2zA5L1yA+oaRHayDnQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-win32-arm64-msvc@14.0.5-canary.17: + resolution: {integrity: sha512-DiTRhyWIIRnkllHbCcjC8gz8igtu7r9/XmnQItgCTHJLpudyQewJ+kq64NKqPzbpO07kN8D6Q+cdOE279z24UQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@next/swc-win32-ia32-msvc@14.0.5-canary.17: + resolution: {integrity: sha512-cfrb2fvzIrYFQMj10xssDD5iRULG3S6EjyqKWUjNChXqqj3xSQoKx4LMOqEQTl0IBVJjHhmDny5xZLwO6F6+aQ==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@next/swc-win32-x64-msvc@14.0.5-canary.17: + resolution: {integrity: sha512-Vc1djW4hEpoc9G63ekNzpFoDq0Zv9XKY3YMyUUP5aKFQgpe72T2GXCSFlVIcTgwoVKiTuDl9wyxEeYpjbMxbWw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@swc/helpers@0.5.2: + resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==} + dependencies: + tslib: 2.6.2 + dev: false + + /@types/node@20.10.5: + resolution: {integrity: sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==} + dependencies: + undici-types: 5.26.5 + dev: true + + /@types/prop-types@15.7.11: + resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==} + dev: true + + /@types/react@18.2.45: + resolution: {integrity: sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==} + dependencies: + '@types/prop-types': 15.7.11 + '@types/scheduler': 0.16.8 + csstype: 3.1.3 + dev: true + + /@types/scheduler@0.16.8: + resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} + dev: true + + /busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + dependencies: + streamsearch: 1.1.0 + dev: false + + /caniuse-lite@1.0.30001570: + resolution: {integrity: sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw==} + dev: false + + /client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + dev: false + + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + dev: true + + /glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + dev: false + + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: false + + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: false + + /loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + dependencies: + js-tokens: 4.0.0 + dev: false + + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false + + /next@14.0.5-canary.17(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-qetVXiLdRjbfvAbZW8MXlY/R0N2BvhkHhNOB2mGmAdOtp4YWDqM1XlYdBwoDhv1LO9sA7WGREczZIp8qyBQxVg==} + engines: {node: '>=18.17.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + sass: + optional: true + dependencies: + '@next/env': 14.0.5-canary.17 + '@swc/helpers': 0.5.2 + busboy: 1.6.0 + caniuse-lite: 1.0.30001570 + graceful-fs: 4.2.11 + postcss: 8.4.31 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + styled-jsx: 5.1.1(react@18.2.0) + watchpack: 2.4.0 + optionalDependencies: + '@next/swc-darwin-arm64': 14.0.5-canary.17 + '@next/swc-darwin-x64': 14.0.5-canary.17 + '@next/swc-linux-arm64-gnu': 14.0.5-canary.17 + '@next/swc-linux-arm64-musl': 14.0.5-canary.17 + '@next/swc-linux-x64-gnu': 14.0.5-canary.17 + '@next/swc-linux-x64-musl': 14.0.5-canary.17 + '@next/swc-win32-arm64-msvc': 14.0.5-canary.17 + '@next/swc-win32-ia32-msvc': 14.0.5-canary.17 + '@next/swc-win32-x64-msvc': 14.0.5-canary.17 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: false + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: false + + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: false + + /react-dom@18.2.0(react@18.2.0): + resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + peerDependencies: + react: ^18.2.0 + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.0 + dev: false + + /react@18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + dev: false + + /scheduler@0.23.0: + resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} + dependencies: + loose-envify: 1.4.0 + dev: false + + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: false + + /streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + dev: false + + /styled-jsx@5.1.1(react@18.2.0): + resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + dependencies: + client-only: 0.0.1 + react: 18.2.0 + dev: false + + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + dev: false + + /typescript@5.3.3: + resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: true + + /watchpack@2.4.0: + resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} + engines: {node: '>=10.13.0'} + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + dev: false diff --git a/src/handle-request.ts b/src/handle-request.ts new file mode 100644 index 0000000..6c89cd5 --- /dev/null +++ b/src/handle-request.ts @@ -0,0 +1,56 @@ +import { NextRequest } from "next/server"; + +const pickHeaders = (headers: Headers, keys: (string | RegExp)[]): Headers => { + const picked = new Headers(); + for (const key of headers.keys()) { + if (keys.some((k) => (typeof k === "string" ? k === key : k.test(key)))) { + const value = headers.get(key); + if (typeof value === "string") { + picked.set(key, value); + } + } + } + return picked; +}; + +const CORS_HEADERS: Record = { + "access-control-allow-origin": "*", + "access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS", + "access-control-allow-headers": "Content-Type", +}; + +export default async function handleRequest(request: NextRequest & { nextUrl?: URL }) { + if (request.method === "OPTIONS") { + return new Response(null, { + headers: CORS_HEADERS, + }); + } + + const { pathname, searchParams } = request.nextUrl ? request.nextUrl : new URL(request.url); + + // curl \ + // -H 'Content-Type: application/json' \ + // -d '{ "prompt": { "text": "Write a story about a magic backpack"} }' \ + // "https://generativelanguage.googleapis.com/v1beta3/models/text-bison-001:generateText?key={YOUR_KEY}" + + const url = new URL(pathname + `?key=${searchParams.get('key')}`, "https://generativelanguage.googleapis.com").href; + const headers = pickHeaders(request.headers, ["content-type"]); + + const response = await fetch(url, { + body: request.body, + method: request.method, + headers, + }); + + const responseHeaders = { + ...CORS_HEADERS, + ...Object.fromEntries( + pickHeaders(response.headers, ["content-type"]) + ), + }; + + return new Response(response.body, { + headers: responseHeaders, + status: response.status + }); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5929c20 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "es2021", + "lib": [ + "es2021" + ], + "jsx": "preserve", + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": false, + "noEmit": true, + "isolatedModules": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "incremental": true, + "esModuleInterop": true + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file